Fix #11463: Incorrect is behaviour with NewType - #11496
Conversation
…literals A NewType instance is, at runtime, an instance of its base type. When narrowing `x is None` or `x is <literal>` for a NewType-typed value, the assignability check used the NewType subclass, so the base type (e.g. None or bool) was not considered assignable and the branch was incorrectly marked unreachable. Unwrap NewType instances to their underlying base type (recursively for nested NewTypes) when testing for None/literal overlap in narrowTypeForIsNone and narrowTypeForLiteralComparison. Fixes microsoft#11463 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Diff from mypy_primer, showing the effect of this PR on open source code: sympy (https://github.com/sympy/sympy)
- .../projects/sympy/sympy/stats/random_matrix_models.py:111:36 - error: Operator "*" not supported for types "Expr" and "tuple[Unknown, ...] | Unknown | Product | Basic | BooleanFalse | BooleanTrue | ComplexInfinity | Equality | Expr | Float | Infinity | Integer | NaN | NegativeInfinity | NegativeOne | Number | One | Rational | Zero"
+ .../projects/sympy/sympy/stats/random_matrix_models.py:111:36 - error: Operator "*" not supported for types "Expr" and "tuple[Unknown, ...] | Unknown | Product | Basic | BooleanFalse | BooleanTrue | Equality | Expr | NaN | One | Zero"
- .../projects/sympy/sympy/stats/rv.py:473:25 - error: Argument of type "Expr | Lambda | Zero | One | Integral | Unknown | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | NaN | Piecewise | Basic | NegativeOne | Integer | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | int" cannot be assigned to parameter "args" of type "Expr | complex" in function "__new__"
- Type "Expr | Lambda | Zero | One | Integral | Unknown | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | NaN | Piecewise | Basic | NegativeOne | Integer | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | int" is not assignable to type "Expr | complex"
+ .../projects/sympy/sympy/stats/rv.py:473:25 - error: Argument of type "Expr | Lambda | Zero | One | Integral | Unknown | NegativeOne | Integer | NaN | ComplexInfinity | Rational | Infinity | Number | NegativeInfinity | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | Piecewise | Basic | int" cannot be assigned to parameter "args" of type "Expr | complex" in function "__new__"
+ Type "Expr | Lambda | Zero | One | Integral | Unknown | NegativeOne | Integer | NaN | ComplexInfinity | Rational | Infinity | Number | NegativeInfinity | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | Piecewise | Basic | int" is not assignable to type "Expr | complex"
+ Type "Basic" is not assignable to type "Expr | complex"
+ "Basic" is not assignable to "Expr"
+ "Basic" is not assignable to "complex" (reportArgumentType)
+ .../projects/sympy/sympy/stats/rv.py:473:25 - error: Argument of type "Mul | Expr | Lambda | Zero | One | Integral | Unknown | NegativeOne | Integer | NaN | ComplexInfinity | Rational | Infinity | Number | NegativeInfinity | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | Piecewise | Basic | int" cannot be assigned to parameter "args" of type "Expr | complex" in function "__new__"
+ Type "Mul | Expr | Lambda | Zero | One | Integral | Unknown | NegativeOne | Integer | NaN | ComplexInfinity | Rational | Infinity | Number | NegativeInfinity | Probability | tuple[Unknown, ...] | Sum | ZeroMatrix | Piecewise | Basic | int" is not assignable to type "Expr | complex"
- Return type mismatch: base method returns type "Self@Basic", override returns type "Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | Self@Expectation | Literal[0]"
+ Return type mismatch: base method returns type "Self@Basic", override returns type "Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | Self@Expectation | Literal[0]"
- Type "Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | Expectation* | Literal[0]" is not assignable to type "Basic"
+ Type "Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | Expectation* | Literal[0]" is not assignable to type "Basic"
- Return type mismatch: base method returns type "Basic", override returns type "Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | Literal[0]"
+ Return type mismatch: base method returns type "Basic", override returns type "Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | Literal[0]"
- Type "Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | Literal[0]" is not assignable to type "Basic"
+ Type "Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | Literal[0]" is not assignable to type "Basic"
+ .../projects/sympy/sympy/stats/tests/test_continuous_rv.py:97:29 - error: Cannot access attribute "symbol" for class "ConditionalFiniteDomain"
+ Attribute "symbol" is unknown (reportAttributeAccessIssue)
- .../projects/sympy/sympy/stats/tests/test_continuous_rv.py:367:12 - error: Operator "-" not supported for types "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" and "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]"
+ .../projects/sympy/sympy/stats/tests/test_continuous_rv.py:367:12 - error: Operator "-" not supported for types "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" and "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]"
- .../projects/sympy/sympy/stats/tests/test_continuous_rv.py:415:21 - error: Argument of type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" cannot be assigned to parameter "expr" of type "Basic" in function "simplify"
+ .../projects/sympy/sympy/stats/tests/test_continuous_rv.py:415:21 - error: Argument of type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" cannot be assigned to parameter "expr" of type "Basic" in function "simplify"
- Type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" is not assignable to type "Basic"
+ Type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" is not assignable to type "Basic"
- .../projects/sympy/sympy/stats/tests/test_continuous_rv.py:1005:21 - error: Argument of type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" cannot be assigned to parameter "expr" of type "Basic" in function "simplify"
+ .../projects/sympy/sympy/stats/tests/test_continuous_rv.py:1005:21 - error: Argument of type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" cannot be assigned to parameter "expr" of type "Basic" in function "simplify"
- Type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" is not assignable to type "Basic"
+ Type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" is not assignable to type "Basic"
- .../projects/sympy/sympy/stats/tests/test_continuous_rv.py:1060:21 - error: Argument of type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" cannot be assigned to parameter "expr" of type "Basic" in function "simplify"
+ .../projects/sympy/sympy/stats/tests/test_continuous_rv.py:1060:21 - error: Argument of type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" cannot be assigned to parameter "expr" of type "Basic" in function "simplify"
- Type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" is not assignable to type "Basic"
+ Type "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" is not assignable to type "Basic"
- .../projects/sympy/sympy/stats/tests/test_continuous_rv.py:1297:61 - error: Operator "**" not supported for types "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Add | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" and "Literal[2]"
+ .../projects/sympy/sympy/stats/tests/test_continuous_rv.py:1297:61 - error: Operator "**" not supported for types "RandomSymbol | Basic | Expectation | Expr | tuple[Unknown, ...] | Unknown | Sum | ZeroMatrix | Zero | NaN | Piecewise | Integral | Any | ExpectationMatrix | Literal[0]" and "Literal[2]"
... (truncated 1476 lines) ...
|
|
Fix looks correct and the sample covers the key cases. Two non-blocking notes below: tighten the test to also assert the unreachable-diagnostic count, and clarify whether negative-direction ( |
|
This change overlaps with an already-landed fix on |
…o the on-disk getInnermostNewTypeBaseInstance helper Adopts main's complete NewType identity-narrowing fix (None/ellipsis/literal, brand-preserving, correct negative branches) and removes the divergent duplicate getNewTypeBaseInstance helper and newType8.py sample. Resolves review feedback on PR microsoft#11496. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
I reconciled this branch with the already-landed fix on |
|
🔒 Automated review in progress — @heejaechang is auto-reviewing this PR. |
|
The narrowing fix itself looks correct and well-targeted. Two follow-ups before merge: add a Pylance-harness regression test per the pylance-pyright-split rule, and update the PR description to match the actual implementation ( |
|
Thanks — addressed the two follow-ups:
Note: after merging current |
Description
Fixes incorrect
isnarrowing forNewTypeinstances. At runtime aNewTypevalue is simply an instance of its base type, but Pyright treatedNewTypeinstances as never overlapping withNoneorboolliterals. This causedx is None/x is True/x is Falsebranches to be wrongly marked unreachable (and narrowing to be skipped) whenxwas aNewTypeoverNoneType,bool, etc.How you figured out what to do
The reported symptom was that comparing a
NewTypeinstance againstNone/bool literals never narrowed and the guarded branch was flagged unreachable. Tracing theisnarrowing logic intypeGuards.tsshowed that both theNonecheck (narrowTypeForIsNone) and the literal-comparison check compared the declaredNewTypeclass against the target, which never overlaps. The fix is to test the underlying base type instead, matching runtime behavior.Implementation
This branch was reconciled with the fix that landed independently on
main. The narrowing uses the on-disk helpergetInnermostNewTypeBaseInstanceintypeGuards.ts, which recursively unwrapsNewTypeclasses down to the innermost base type instance (or returnsundefinedif the type isn't aNewType):narrowTypeForIsNone: uses the unwrapped innermost base instance when testing for overlap withNone. The negative branch is eliminated (returnsundefined) only when the innermost base is exactlyNone(isNoneInstance), so aNewTypeoverNonecorrectly makes theis not Nonebranch unreachable, while a None-compatible base likeNewType("Obj", object)stays reachable.NewTypebrand on the positive branch (return isClassInstance(subtype) && ClassType.isNewTypeClass(subtype) ? subtype : literalType;), sob is Truenarrows to theNewType(e.g.Banana) rather thanLiteral[True], avoiding a spuriousreportArgumentTypeon a later construction.Non-
NewTypetypes fall back to the original subtype, so existing behavior is unchanged.Testing
Regression coverage is provided by sample
newTypeIsLiteral1.py, wired up asNewType8intypeEvaluator2.test.ts. The test runs withreportUnreachable = 'error'and assertsvalidateResults(analysisResults, 0)— any branch wrongly greyed out would surface as areportUnreachableerror and fail. It covers:NewTypeoverNoneTypenarrowed byis None/is not None(exactly-None positive and negative directions).is True/is False) that preserves theNewTypebrand.NewType("Fig", object),NewType("Guava", object)) that must stay reachable onis not None/is not ...and not collapse toNever.Addresses
Fixes #11463