Skip to content

Fix #11463: Incorrect is behaviour with NewType#11496

Open
rchiodo wants to merge 2 commits into
microsoft:mainfrom
rchiodo:fix11463
Open

Fix #11463: Incorrect is behaviour with NewType#11496
rchiodo wants to merge 2 commits into
microsoft:mainfrom
rchiodo:fix11463

Conversation

@rchiodo

@rchiodo rchiodo commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Description

Fixes incorrect is narrowing for NewType instances. At runtime a NewType value is simply an instance of its base type, but Pyright treated NewType instances as never overlapping with None or bool literals. This caused x is None / x is True / x is False branches to be wrongly marked unreachable (and narrowing to be skipped) when x was a NewType over NoneType, bool, etc.

How you figured out what to do

The reported symptom was that comparing a NewType instance against None/bool literals never narrowed and the guarded branch was flagged unreachable. Tracing the is narrowing logic in typeGuards.ts showed that both the None check (narrowTypeForIsNone) and the literal-comparison check (narrowTypeForLiteralComparison) compared the declared NewType class against the target, which never overlaps. The fix is to test the underlying base type instead, matching runtime behavior.

Implementation

Added a helper getNewTypeBaseInstance in typeGuards.ts that, given a class instance, recursively unwraps NewType classes (following the first base class) and returns the underlying base type instance — or undefined if the type isn't a NewType.

  • narrowTypeForIsNone: use the unwrapped base instance when testing for overlap with None.
  • narrowTypeForLiteralComparison: use the unwrapped base instance when testing for overlap with a literal (e.g. True/False).

Non-NewType types fall back to the original subtype, so existing behavior is unchanged.

Testing

Added sample newType8.py covering:

  • NewType over NoneType narrowed by is NoneNone.
  • NewType over bool narrowed by is True / is FalseLiteral[True] / Literal[False].
  • A recursive NewType (base is itself a NewType) narrowed by is None.
  • A NewType over int, where is None correctly remains unreachable (validating we don't over-narrow).

Wired it up as NewType8 in typeEvaluator2.test.ts (expects 1 error from the intentional unreachable branch).

Addresses

Fixes https://github.com/microsoft/pylance-release/issues/11463


Generated by fix_all_my_issues pipeline

…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>
@github-actions

Copy link
Copy Markdown
Contributor

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) ...

Comment thread packages/pyright-internal/src/tests/typeEvaluator2.test.ts
Comment thread packages/pyright-internal/src/analyzer/typeGuards.ts
@rchiodo

rchiodo commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

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 (is not/else) narrowing for NewType is intentionally out of scope.

Comment thread packages/pyright-internal/src/analyzer/typeGuards.ts
Comment thread packages/pyright-internal/src/analyzer/typeGuards.ts
Comment thread packages/pyright-internal/src/analyzer/typeGuards.ts Outdated
Comment thread packages/pyright-internal/src/tests/samples/newType8.py Outdated
Comment thread packages/pyright-internal/src/tests/samples/newType8.py Outdated
@heejaechang

Copy link
Copy Markdown
Collaborator

This change overlaps with an already-landed fix on main: getInnermostNewTypeBaseInstance in typeGuards.ts already implements NewType is narrowing, and it diverges from this PR on two behaviors (handling of is not None for None-based NewTypes, and preserving the NewType brand on literal narrowing). Please reconcile to the single on-disk helper/semantic rather than landing a second near-duplicate, and update newType8.py expectations to match.

…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>
@rchiodo

rchiodo commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

I reconciled this branch with the already-landed fix on main. I merged current upstream/main and dropped the divergent getNewTypeBaseInstance helper plus the newType8.py sample, so the branch now uses the single on-disk getInnermostNewTypeBaseInstance helper and main's complete narrowing semantics (None/ellipsis/literal, brand-preserving, correct negative branches; covered by newTypeIsLiteral1.py).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants