Skip to content

Commit

Permalink
Fixed a bug that results in unexpected constraint solver results in c…
Browse files Browse the repository at this point in the history
…ertain cases involving arguments with lambda expressions. This addresses #8697.
  • Loading branch information
erictraut committed Oct 31, 2024
1 parent e054478 commit c8ed71a
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 23 deletions.
59 changes: 36 additions & 23 deletions packages/pyright-internal/src/analyzer/typeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4202,30 +4202,43 @@ class ApplySolvedTypeVarsTransformer extends TypeVarTransformer {
// in cases where TypeVars can go unsolved due to unions in parameter
// annotations, like this:
// def test(x: Union[str, T]) -> Union[str, T]
if (this._options.replaceUnsolved?.eliminateUnsolvedInUnions) {
if (
isTypeVar(preTransform) &&
this._shouldReplaceTypeVar(preTransform) &&
this._shouldReplaceUnsolvedTypeVar(preTransform)
) {
const solutionSet = this._solution.getSolutionSet(this._activeConstraintSetIndex ?? 0);
const typeVarType = solutionSet.getType(preTransform);

// Did the TypeVar remain unsolved?
if (!typeVarType || (isTypeVar(typeVarType) && TypeVarType.isUnification(typeVarType))) {
// If the TypeVar was not transformed, then it was unsolved,
// and we'll eliminate it.
if (preTransform === postTransform) {
return undefined;
}
if (!this._options.replaceUnsolved?.eliminateUnsolvedInUnions) {
return postTransform;
}

// If useDefaultForUnsolved or useUnknownForUnsolved is true, the postTransform type will
// be Unknown, which we want to eliminate.
if (this._options.replaceUnsolved) {
if (isUnknown(postTransform)) {
return undefined;
}
}
const solutionSet = this._solution.getSolutionSet(this._activeConstraintSetIndex ?? 0);

if (isTypeVar(preTransform)) {
if (!this._shouldReplaceTypeVar(preTransform) || !this._shouldReplaceUnsolvedTypeVar(preTransform)) {
return postTransform;
}

const typeVarType = solutionSet.getType(preTransform);

// Did the TypeVar remain unsolved?
if (typeVarType) {
if (!isTypeVar(typeVarType) || !TypeVarType.isUnification(typeVarType)) {
return postTransform;
}
}

// If the TypeVar was not transformed, then it was unsolved,
// and we'll eliminate it.
if (preTransform === postTransform) {
return undefined;
}

// If useDefaultForUnsolved or useUnknownForUnsolved is true, the postTransform type will
// be Unknown, which we want to eliminate.
if (this._options.replaceUnsolved && isUnknown(postTransform)) {
return undefined;
}
} else if (preTransform.props?.condition) {
// If this is a type that is conditioned on a unification TypeVar,
// see if TypeVar was solved. If not, eliminate the type.
for (const condition of preTransform.props.condition) {
if (TypeVarType.isUnification(condition.typeVar) && !solutionSet.getType(condition.typeVar)) {
return undefined;
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions packages/pyright-internal/src/tests/samples/solver44.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# This sample tests the case where the solver generates an unsolved
# unification variable that has been specialized into a conditional type.

from typing import Callable, Iterable, Self


class map[S]:
def __new__[T](cls, func: Callable[[T], S], iter1: Iterable[T]) -> Self: ...


def func(a: list[int | None]):
b = map(lambda x: x or 0, a)
reveal_type(b, expected_text="map[int]")
6 changes: 6 additions & 0 deletions packages/pyright-internal/src/tests/typeEvaluator2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,12 @@ test('Solver43', () => {
TestUtils.validateResults(analysisResults, 0);
});

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

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

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

Expand Down

0 comments on commit c8ed71a

Please sign in to comment.