Skip to content

Commit

Permalink
Fixed bug that results in a spurious error when an assignment express…
Browse files Browse the repository at this point in the history
…ion (walrus operator) is used as an argument to a constructor call when the constructor has both a `__new__` and `__init__` with differing bidirectional type inference contexts. This addresses #9659. (#9699)
  • Loading branch information
erictraut authored Jan 14, 2025
1 parent fb7989c commit 4d8b903
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 12 deletions.
24 changes: 14 additions & 10 deletions packages/pyright-internal/src/analyzer/constructors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,16 +423,20 @@ function validateNewMethod(

const constraints = new ConstraintTracker();

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

if (callResult.isTypeIncomplete) {
isTypeIncomplete = true;
Expand Down
2 changes: 1 addition & 1 deletion packages/pyright-internal/src/analyzer/typeCacheUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export interface SpeculativeModeOptions {
// a context is popped off the stack, all of the speculative type cache
// entries that were created within that context are removed from the
// corresponding type caches because they are no longer valid.
// Each type context also contains a map of "speculative types" that are
// The tracker also also contains a map of "speculative types" that are
// contextually evaluated based on an "expected type" and potentially
// one or more "dependent types". The "expected type" applies in cases
// where the speculative root node is being evaluated with bidirectional
Expand Down
7 changes: 6 additions & 1 deletion packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { Declaration } from './declaration';
import { ResolvedAliasInfo } from './declarationUtils';
import { SymbolWithScope } from './scope';
import { Symbol, SynthesizedTypeInfo } from './symbol';
import { SpeculativeModeOptions } from './typeCacheUtils';
import { PrintTypeFlags } from './typePrinter';
import {
AnyType,
Expand Down Expand Up @@ -843,7 +844,11 @@ export interface TypeEvaluator {

getTypeCacheEntryCount: () => number;
disposeEvaluator: () => void;
useSpeculativeMode: <T>(speculativeNode: ParseNode | undefined, callback: () => T) => T;
useSpeculativeMode: <T>(
speculativeNode: ParseNode | undefined,
callback: () => T,
options?: SpeculativeModeOptions
) => T;
isSpeculativeModeInUse: (node: ParseNode | undefined) => boolean;
setTypeResultForNode: (node: ParseNode, typeResult: TypeResult, flags?: EvalFlags) => void;

Expand Down
14 changes: 14 additions & 0 deletions packages/pyright-internal/src/tests/samples/constructor33.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This sample tests the case where an argument to a constructor
# uses an assignment expression (walrus operator) and the constructor
# has both a __new__ and __init__ method whose parameters have
# different bidirectional type inference contexts.

from typing import Any, Self


class A:
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
def __init__(self, base: list[str], joined: str) -> None: ...


A(temp := ["x"], " ".join(temp))
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 @@ -849,6 +849,12 @@ test('Constructor32', () => {
TestUtils.validateResults(analysisResults, 1);
});

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

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

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

Expand Down

0 comments on commit 4d8b903

Please sign in to comment.