From b7a7fc6809616eaf3d8145b5b8264e279941f47d Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 20 Oct 2024 18:35:42 -0700 Subject: [PATCH 1/2] Fixed bug that results in a false negative when the specialization of a ParamSpec results in a signature that has a duplicate parameter name where one parameter has a default value and the other does not. This addresses #9239. --- .../src/analyzer/typeEvaluator.ts | 9 +++-- .../src/tests/samples/paramSpec3.py | 38 ++++++++++++------- .../src/tests/typeEvaluator4.test.ts | 2 +- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 094b3951d68a..b51b42031926 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -10442,10 +10442,11 @@ export function createTypeEvaluator( const param = paramInfo.param; if (param.name && param.category === ParamCategory.Simple && paramInfo.kind !== ParamKind.Positional) { - paramMap.set(param.name, { - argsNeeded: param.category === ParamCategory.Simple && !paramInfo.defaultType ? 1 : 0, - argsReceived: 0, - }); + let argsNeeded = paramMap.get(param.name)?.argsNeeded ?? 0; + if (param.category === ParamCategory.Simple && !paramInfo.defaultType) { + argsNeeded += 1; + } + paramMap.set(param.name, { argsNeeded, argsReceived: 0 }); } }); diff --git a/packages/pyright-internal/src/tests/samples/paramSpec3.py b/packages/pyright-internal/src/tests/samples/paramSpec3.py index b07a9cca4898..84986276b1eb 100644 --- a/packages/pyright-internal/src/tests/samples/paramSpec3.py +++ b/packages/pyright-internal/src/tests/samples/paramSpec3.py @@ -35,13 +35,11 @@ async def func2(): @overload -def func3(x: int) -> None: - ... +def func3(x: int) -> None: ... @overload -def func3(x: str) -> str: - ... +def func3(x: str) -> str: ... def func3(x: int | str) -> str | None: @@ -75,8 +73,7 @@ def decorator2(f: Callable[P, R]) -> Callable[P, R]: def func5(f: Callable[[], list[T1]]) -> Callable[[list[T2]], list[T1 | T2]]: - def inner(res: list[T2], /) -> list[T1 | T2]: - ... + def inner(res: list[T2], /) -> list[T1 | T2]: ... return decorator2(inner) @@ -90,22 +87,18 @@ def inner(*args: P.args, **kwargs: P.kwargs) -> None: class Callback1: - def __call__(self, x: int | str, y: int = 3) -> None: - ... + def __call__(self, x: int | str, y: int = 3) -> None: ... class Callback2: - def __call__(self, x: int, /) -> None: - ... + def __call__(self, x: int, /) -> None: ... class Callback3: - def __call__(self, *args, **kwargs) -> None: - ... + def __call__(self, *args, **kwargs) -> None: ... -def func7(f1: Callable[P, R], f2: Callable[P, R]) -> Callable[P, R]: - ... +def func7(f1: Callable[P, R], f2: Callable[P, R]) -> Callable[P, R]: ... def func8(cb1: Callback1, cb2: Callback2, cb3: Callback3): @@ -119,3 +112,20 @@ def func8(cb1: Callback1, cb2: Callback2, cb3: Callback3): def func9(f: Callable[P, object], *args: P.args, **kwargs: P.kwargs) -> object: # This should generate an error because "name" doesn't exist. return f(*args, **kwargs, name="") + + +def func10(data: int = 1) -> None: + pass + + +def func11[**P]( + cls: Callable[P, None], data: str, *args: P.args, **kwargs: P.kwargs +) -> None: ... + + +func11(func10, "") +func11(func10, "", 0) + +# This should generate an error because one of the two "data" parameters +# does not have a default value. +func11(func10) diff --git a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts index 6d91439e550a..0648f0257203 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts @@ -582,7 +582,7 @@ test('ParamSpec2', () => { test('ParamSpec3', () => { const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec3.py']); - TestUtils.validateResults(results, 2); + TestUtils.validateResults(results, 3); }); test('ParamSpec4', () => { From 182ef9c2d2a8b70cc4206b57e40154dbe3bed22a Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 21 Oct 2024 10:29:58 -0700 Subject: [PATCH 2/2] Fixed bug that can lead to a false negative when referencing an outer-scoped symbol in a method parameter type annotation if the method is using PEP 695 type parameter syntax. This addresses #9280. --- .../src/analyzer/parseTreeUtils.ts | 5 ++ .../src/analyzer/typeEvaluator.ts | 21 +++++-- .../src/tests/samples/typeParams1.py | 55 +++++++++---------- .../src/tests/typeEvaluator5.test.ts | 2 +- 4 files changed, 49 insertions(+), 34 deletions(-) diff --git a/packages/pyright-internal/src/analyzer/parseTreeUtils.ts b/packages/pyright-internal/src/analyzer/parseTreeUtils.ts index f7e046be54dc..2c99118b1f69 100644 --- a/packages/pyright-internal/src/analyzer/parseTreeUtils.ts +++ b/packages/pyright-internal/src/analyzer/parseTreeUtils.ts @@ -930,6 +930,11 @@ export function getEvaluationScopeNode(node: ParseNode): EvaluationScopeInfo { break; } + // The name of the function is evaluated within the containing scope. + if (prevNode === curNode.d.name) { + break; + } + if (curNode.d.params.some((param) => param === prevNode)) { // Default argument expressions are evaluated outside of the function scope. if (isParamDefaultNode) { diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index b51b42031926..48df7c860d8c 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -21160,16 +21160,27 @@ export function createTypeEvaluator( // Filter the declarations based on flow reachability. const reachableDecl = symbolWithScope.symbol.getDeclarations().find((decl) => { if (decl.type !== DeclarationType.Alias && decl.type !== DeclarationType.Intrinsic) { - // Is the declaration in the same execution scope as the "usageNode" node? - const usageScope = ParseTreeUtils.getExecutionScopeNode(node); - const declNode = + // Determine if the declaration in the same execution scope as the "usageNode" node. + let usageScopeNode = ParseTreeUtils.getExecutionScopeNode(node); + const declNode: ParseNode = decl.type === DeclarationType.Class || decl.type === DeclarationType.Function || decl.type === DeclarationType.TypeAlias ? decl.node.d.name : decl.node; - const declScope = ParseTreeUtils.getExecutionScopeNode(declNode); - if (usageScope === declScope) { + const declScopeNode = ParseTreeUtils.getExecutionScopeNode(declNode); + + // If this is a type parameter scope, it will be a proxy for its + // containing scope, so we need to use that instead. + const usageScope = AnalyzerNodeInfo.getScope(usageScopeNode); + if (usageScope?.proxy) { + const typeParamScope = AnalyzerNodeInfo.getScope(usageScopeNode); + if (!typeParamScope?.symbolTable.has(name) && usageScopeNode.parent) { + usageScopeNode = ParseTreeUtils.getExecutionScopeNode(usageScopeNode.parent); + } + } + + if (usageScopeNode === declScopeNode) { if (!isFlowPathBetweenNodes(declNode, node)) { // If there was no control flow path from the usage back // to the source, see if the usage node is reachable by diff --git a/packages/pyright-internal/src/tests/samples/typeParams1.py b/packages/pyright-internal/src/tests/samples/typeParams1.py index 021c0842da23..2edcb758a3ff 100644 --- a/packages/pyright-internal/src/tests/samples/typeParams1.py +++ b/packages/pyright-internal/src/tests/samples/typeParams1.py @@ -4,37 +4,30 @@ T1 = 0 -class ClassA[T1]: - ... +class ClassA[T1]: ... -def func1[T1](): - ... +def func1[T1](): ... T2: str -class ClassB[T2]: - ... +class ClassB[T2]: ... -def func2[T2](): - ... +def func2[T2](): ... # This should generate an error because T3 is duplicated. -class ClassC[T3, S1, T3]: - ... +class ClassC[T3, S1, T3]: ... class ClassD: - class ClassE: - ... + class ClassE: ... class ClassF: - class A[T]: - ... + class A[T]: ... int_alias = int @@ -51,35 +44,28 @@ class ClassG[T](list["T"]): class ClassH: - def object[T](self, target: object, new: T) -> T: - ... + def object[T](self, target: object, new: T) -> T: ... # This should generate an error because T3 is duplicated. -def func3[T3, S1, T3](): - ... +def func3[T3, S1, T3](): ... -def func4[T4](T4: int): - ... +def func4[T4](T4: int): ... def func5[T5](a: int): # This should generate an error because T5 is already in use. - class ClassA[T5]: - ... + class ClassA[T5]: ... # This should generate an error because T5 is already in use. - def inner_func1[T5](): - ... + def inner_func1[T5](): ... def func6[T6](T7: int): - class ClassA[T7]: - ... + class ClassA[T7]: ... - def inner_func1[T7](): - ... + def inner_func1[T7](): ... global T2 @@ -102,3 +88,16 @@ def func8[T10: (ForwardRefClass[str], "ForwardRefClass[int]")](): class ForwardRefClass[T]: pass + + +class ClassI1: ... + + +class ClassI2: + def method1[T](self, v: ClassI1) -> None: ... + + # This should generate an error because ClassJ is + def method2[T](self, v: ClassI3) -> None: ... + + +class ClassI3: ... diff --git a/packages/pyright-internal/src/tests/typeEvaluator5.test.ts b/packages/pyright-internal/src/tests/typeEvaluator5.test.ts index 7adf8b36b444..f1259b71475d 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator5.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator5.test.ts @@ -18,7 +18,7 @@ test('TypeParams1', () => { configOptions.defaultPythonVersion = pythonVersion3_12; const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeParams1.py'], configOptions); - TestUtils.validateResults(analysisResults, 5); + TestUtils.validateResults(analysisResults, 6); }); test('TypeParams2', () => {