Skip to content

Enable TypeForm support by default#11412

Open
davidfstr wants to merge 6 commits intomicrosoft:mainfrom
davidfstr:f/typeform_on
Open

Enable TypeForm support by default#11412
davidfstr wants to merge 6 commits intomicrosoft:mainfrom
davidfstr:f/typeform_on

Conversation

@davidfstr
Copy link
Copy Markdown

...rather than requiring the --enableExperimentalFeatures bit to be set


CONTEXT:

  • PEP 747 (TypeForm) is accepted for Python 3.15 which is being finalized (feature-wise), so it seems like a good time to turn on TypeForm recognition in pyright by default.

OPEN QUESTIONS:

  • I am curious to know what the performance implications (if any) are for enabling recognition of TypeForms everywhere by default. Does your CI infrastructure have statistics that could be used to detect performance changes? Maybe mypy_primer could be run in a mode that checks performance deltas?

...rather than requiring the --enableExperimentalFeatures bit to be set
...by the current interence context or because EvalFlags.TypeFormArg is set.

Without this optimization, the call5.py test fails where it defines a
NamedTuple "X", [("a", int), ...]. Attempting to interpret the string "a"
as a forward reference looks up the name `a` (bound by a later for-loop),
and the resulting flow analysis recursively re-enters the Z(...) constructor,
causing a "Class definition for Z depends on itself" error.
@davidfstr
Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Misspelling in commit message: "current interence context" -> "current inference context"

Comment thread packages/pyright-internal/src/analyzer/typeEvaluator.ts Outdated
Comment thread packages/pyright-internal/src/analyzer/typeEvaluator.ts
Comment thread plan/test-output.txt Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Diff from mypy_primer, showing the effect of this PR on open source code:

sympy (https://github.com/sympy/sympy)
+   .../projects/sympy/sympy/polys/euclidtools.py:1974:24 - error: Argument of type "dmp[Er@dmp_cancel] | Unknown | dup[Unknown]" cannot be assigned to parameter "f" of type "dmp[Er@dmp_mul_ground]" in function "dmp_mul_ground"
+     Type "dmp[Er@dmp_cancel] | Unknown | dup[Unknown]" is not assignable to type "dmp[Er@dmp_mul_ground]"
+       "builtins.list" is not assignable to "builtins.list"
+         Type parameter "_T@list" is invariant, but "dmp" is not the same as "dmp"
+         Consider switching from "list" to "Sequence" which is covariant (reportArgumentType)
+   .../projects/sympy/sympy/polys/euclidtools.py:1974:34 - error: Argument of type "Domain[Er@dmp_cancel] | Ring[Unknown]" cannot be assigned to parameter "K" of type "Domain[Er@dmp_mul_ground]" in function "dmp_mul_ground"
+     Type "Domain[Er@dmp_cancel] | Ring[Unknown]" is not assignable to type "Domain[Er@dmp_mul_ground]"
+       "Domain[Er@dmp_cancel]" is not assignable to "Domain[Er@dmp_mul_ground]"
+         Type parameter "Er@Domain" is invariant, but "Er@dmp_cancel" is not the same as "Er@dmp_mul_ground" (reportArgumentType)
-   .../projects/sympy/sympy/polys/euclidtools.py:1976:12 - error: Type "tuple[dmp[Er@dmp_cancel | Unknown | RingElement*], dmp[Er@dmp_cancel]]" is not assignable to return type "tuple[dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]] | tuple[Er@dmp_cancel, Er@dmp_cancel, dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]]"
+   .../projects/sympy/sympy/polys/euclidtools.py:1976:12 - error: Type "tuple[dmp[Er@dmp_cancel | Unknown | RingElement*], dmp[Er@dmp_cancel | Unknown]]" is not assignable to return type "tuple[dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]] | tuple[Er@dmp_cancel, Er@dmp_cancel, dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]]"
-     Type "tuple[dmp[Er@dmp_cancel | Unknown | RingElement*], dmp[Er@dmp_cancel]]" is not assignable to type "tuple[dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]] | tuple[Er@dmp_cancel, Er@dmp_cancel, dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]]"
+     Type "tuple[dmp[Er@dmp_cancel | Unknown | RingElement*], dmp[Er@dmp_cancel | Unknown]]" is not assignable to type "tuple[dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]] | tuple[Er@dmp_cancel, Er@dmp_cancel, dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]]"
-       "tuple[dmp[Er@dmp_cancel | Unknown | RingElement*], dmp[Er@dmp_cancel]]" is not assignable to "tuple[Er@dmp_cancel, Er@dmp_cancel, dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]]"
+       "tuple[dmp[Er@dmp_cancel | Unknown | RingElement*], dmp[Er@dmp_cancel | Unknown]]" is not assignable to "tuple[Er@dmp_cancel, Er@dmp_cancel, dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]]"
-       "tuple[dmp[Er@dmp_cancel | Unknown | RingElement*], dmp[Er@dmp_cancel]]" is not assignable to "tuple[dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]]"
+       "tuple[dmp[Er@dmp_cancel | Unknown | RingElement*], dmp[Er@dmp_cancel | Unknown]]" is not assignable to "tuple[dmp[Er@dmp_cancel], dmp[Er@dmp_cancel]]"
-   .../projects/sympy/sympy/solvers/bivariate.py:135:15 - error: Operator "-" not supported for "None" (reportOptionalOperand)
-   .../projects/sympy/sympy/solvers/bivariate.py:139:17 - error: Operator "-" not supported for type "Basic | Unknown" (reportOperatorIssue)
-   .../projects/sympy/sympy/solvers/bivariate.py:144:23 - error: Operator "-" not supported for "None" (reportOptionalOperand)
-   .../projects/sympy/sympy/solvers/deutils.py:234:14 - error: Operator "not in" not supported for types "str" and "Unknown | int"
-     Operator "not in" not supported for types "str" and "int" (reportOperatorIssue)
-   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:176:44 - error: Cannot access attribute "expand" for class "Basic"
-   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:423:38 - error: Argument of type "Unknown | Expr | Literal[0]" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:423:38 - error: Argument of type "int | Expr" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
-     Type "Unknown | Expr | Literal[0]" is not assignable to type "Expr"
+     Type "int | Expr" is not assignable to type "Expr"
-       "Literal[0]" is not assignable to "Expr" (reportArgumentType)
+       "int" is not assignable to "Expr" (reportArgumentType)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:504:19 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+     Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:505:19 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+     Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:506:19 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+     Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:569:42 - error: Operator "*" not supported for types "int" and "Unknown | Basic"
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:569:50 - error: Operator "*" not supported for types "Expr" and "Unknown | Basic"
+     Operator "*" not supported for types "Expr" and "Basic" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:725:42 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+     Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:735:19 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+     Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:736:19 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+     Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:737:19 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+     Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:814:47 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+     Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:841:26 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+     Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:842:22 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+     Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:843:36 - error: Operator "*" not supported for types "int" and "Unknown | Basic"
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:843:42 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+     Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:843:51 - error: Operator "*" not supported for types "int" and "Unknown | Basic"
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:843:57 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+     Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:843:66 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+     Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:845:53 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+     Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:854:18 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+     Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:856:22 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+     Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:865:22 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+     Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:865:36 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+     Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:867:27 - error: Operator "**" not supported for types "Unknown | Basic" and "Literal[2]"
+     Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/diophantine/diophantine.py:868:27 - error: Operator "*" not supported for types "Unknown | Basic" and "Unknown | Basic"
+     Operator "*" not supported for types "Basic" and "Basic" (reportOperatorIssue)

... (truncated 1245 lines) ...

@rchiodo
Copy link
Copy Markdown
Collaborator

rchiodo commented May 1, 2026

There's a prettier error too:

check:prettier
prettier -c .

Checking formatting...
[warn] packages/pyright-internal/src/analyzer/typeEvaluator.ts
[warn] Code style issues found in the above file. Forgot to run Prettier?

You should be able to run npm run fix:prettier from the command line and submit the results.

@davidfstr
Copy link
Copy Markdown
Author

  • Will fix prettier issues, and anything else reported by CI
  • Will investigate mypy_primer output.

davidfstr added 4 commits May 3, 2026 07:47
Squish change to -> Do not try to interpret a string as a TypeForm unless one is expected
Squish to -> Do not try to interpret a string as a TypeForm unless one is expected
Squish to -> Enable TypeForm support by default
@correctmost
Copy link
Copy Markdown

  • I am curious to know what the performance implications (if any) are for enabling recognition of TypeForms everywhere by default

I previously reported a performance issue with TypeForm support: #11008. This PR mostly fixes that large slowdown, but it seems like Pyright is still a little slower with TypeForm support enabled for that testcase.

@davidfstr
Copy link
Copy Markdown
Author

Analysis of mypy_primer output:

  • The only project tracked by mypy-primer that had any change in error counts was sympy (https://github.com/sympy/sympy)
  • sympy had preexisting errors when run under pyright, as indicated by mypy-primer showing removed lines in the diff
  • Many of the preexisting errors changed to a slightly different form after the TypeForm enablement change
    • Specifically, 121 locations have both removed and added errors at the same location, indicating modified error messages rather than new errors.
    • ✅ I'm not too concerned about preexisting errors changing. Probably they changed to be a more narrow/precise finding.
  • Some preexisting errors were removed
    • Specifically, 33 locations had errors that disappeared entirely
    • ✅ I'm not too concerned about errors being removed
  • There do exist new errors:
    • ⚠️ Specifically, 171 locations have errors that are completely new (no prior error at that
      location)
    • The new errors are concentrated in:
      • 👉 polys/euclidtools.py: Lines 1974 (type parameter variance issues)
      • 👉 solvers/diophantine/diophantine.py: Heavy concentration (504-912+) with operator type errors like Operator "*" not supported for types "Unknown | Basic"

Analysis of new errors in diophantine.py

Errors at diophantine.py:175-183 are in the DiophantineEquationType.__init__ function, and involve the unannotated equation parameter variable:

class DiophantineEquationType:
    """..."""
    name: str

    def __init__(self, equation, free_symbols=None):  # LINE 175
        self.equation = _sympify(equation).expand(force=True)

        if free_symbols is not None:
            self.free_symbols = free_symbols
        else:
            self.free_symbols = list(self.equation.free_symbols)
            self.free_symbols.sort(key=default_sort_key)  # LINE 182
  • With TypeForm enabled, _sympify(equation) (where equation is unannotated) resolves to the first matching overload of sympify (returning Integer), not the last fallback (returning Basic). This single difference cascades through the rest of the function. For reference, _sympify has the following overloads:
@overload
def sympify(a: int, *, strict: bool = False) -> Integer: ... # type: ignore
@overload
def sympify(a: float, *, strict: bool = False) -> Float: ...
@overload
def sympify(a: Expr | complex, *, strict: bool = False) -> Expr: ...
@overload
def sympify(a: Tbasic, *, strict: bool = False) -> Tbasic: ...
@overload
def sympify(a: Any, *, strict: bool = False) -> Basic: ...

def sympify(a, locals=None, convert_xor=True, strict=False, rational=False,
        evaluate=None): ...
  • It is not clear to me why enabling TypeForm would alter the chosen overload in this situation. If an unannotated equation was passed to sympify(equation), I'd expect pyright to presume that equation: Unknown (which is equivalent to equation: Any AFAIK) and thus select the def sympify(a: Any, *, strict: bool = False) -> Basic: ... overload, which is the behavior that existed before. I plan to investigate this change in overload section further.

  • All of the new errors at diophantine.py:504+ are cascading consequences of the differing overload selected.

For reference, the revealed types within diophantine.py:175-183 changed in the following ways:

  ┌────────────────────────────┬─────────────────────┬──────────────────────┐
  │                            │ Baseline (TypeForm  │   TypeForm enabled   │
  │                            │        off)         │                      │
  ├────────────────────────────┼─────────────────────┼──────────────────────┤
  │                            │ Basic (last         │ Integer (first       │
  │ _sympify(equation)         │ overload, a: Any →  │ overload, a: int →   │
  │                            │ Basic)              │ Integer)             │
  ├────────────────────────────┼─────────────────────┼──────────────────────┤
  │ .expand(force=True)        │ error: Basic has no │ Expr (inherited)     │
  │                            │  .expand → Unknown  │                      │
  ├────────────────────────────┼─────────────────────┼──────────────────────┤
  │ self.equation              │ Unknown             │ Expr                 │
  ├────────────────────────────┼─────────────────────┼──────────────────────┤
  │ self.equation.free_symbols │ Unknown             │ set[Basic]           │
  │                            │                     │ (annotated)          │
  ├────────────────────────────┼─────────────────────┼──────────────────────┤
  │ self.free_symbols          │ Unknown |           │ Unknown |            │
  │                            │ list[Unknown]       │ list[Basic]          │
  ├────────────────────────────┼─────────────────────┼──────────────────────┤
  │ x, y = var                 │ x: Unknown          │ x: Unknown | Basic   │
  ├────────────────────────────┼─────────────────────┼──────────────────────┤
  │ x**2 at line 504           │ OK (Unknown)        │ error — Basic has no │
  │                            │                     │  __pow__             │
  └────────────────────────────┴─────────────────────┴──────────────────────┘

Analysis of new errors in euclidtools.py

Affected code:

def dmp_cancel(...) -> tuple[dmp[Er], dmp[Er]] | tuple[Er, Er, dmp[Er], dmp[Er]]:
    ...

    p = dmp_mul_ground(p, cp, u, K)  # LINE 1973
    q = dmp_mul_ground(q, cq, u, K)  # LINE 1974

    return p, q  # LINE 1976

One new error at line 1974 (q = dmp_mul_ground(q, cq, u, K)):

+   .../projects/sympy/sympy/polys/euclidtools.py:1974:24 - error: Argument of type "dmp[Er@dmp_cancel] | Unknown | dup[Unknown]" cannot be assigned to parameter "f" of type "dmp[Er@dmp_mul_ground]" in function "dmp_mul_ground"
+     Type "dmp[Er@dmp_cancel] | Unknown | dup[Unknown]" is not assignable to type "dmp[Er@dmp_mul_ground]"
+       "builtins.list" is not assignable to "builtins.list"
+         Type parameter "_T@list" is invariant, but "dmp" is not the same as "dmp"
+         Consider switching from "list" to "Sequence" which is covariant (reportArgumentType)
+   .../projects/sympy/sympy/polys/euclidtools.py:1974:34 - error: Argument of type "Domain[Er@dmp_cancel] | Ring[Unknown]" cannot be assigned to parameter "K" of type "Domain[Er@dmp_mul_ground]" in function "dmp_mul_ground"
+     Type "Domain[Er@dmp_cancel] | Ring[Unknown]" is not assignable to type "Domain[Er@dmp_mul_ground]"
+       "Domain[Er@dmp_cancel]" is not assignable to "Domain[Er@dmp_mul_ground]"
+         Type parameter "Er@Domain" is invariant, but "Er@dmp_cancel" is not the same as "Er@dmp_mul_ground" (reportArgumentType)

The two new errors at line 1974 are word-for-word identical to the two errors already at line 1973 (with q/cq substituted for p/cp).

On main branch (Baseline) vs. this feature branch (TypeForm), the inputs to this section of code are identical:

  ┌─────────────────┬───────────────────────────────────────────────────────┐
  │    Variable     │                   Type (both modes)                   │
  ├─────────────────┼───────────────────────────────────────────────────────┤
  │ p (before line  │ list[dmp] | Unknown | list[Unknown]                   │
  │ 1973)           │                                                       │
  ├─────────────────┼───────────────────────────────────────────────────────┤
  │ q (before line  │ list[dmp] | Unknown | list[Unknown] ← same as p       │
  │ 1974)           │                                                       │
  ├─────────────────┼───────────────────────────────────────────────────────┤
  │ cp              │ Er@dmp_cancel | Unknown | RingElement* (3-way,        │
  │                 │ includes TypeVar bound)                               │
  ├─────────────────┼───────────────────────────────────────────────────────┤
  │ cq              │ Er@dmp_cancel | Unknown (2-way, no bound)             │
  ├─────────────────┼───────────────────────────────────────────────────────┤
  │ K               │ Domain[Er@dmp_cancel] | Ring[Unknown]                 │
  └─────────────────┴───────────────────────────────────────────────────────┘

So p and q have identical types going in, yet:

  • Baseline: flags dmp_mul_ground(p, cp, u, K) but silently accepts
    dmp_mul_ground(q, cq, u, K).
  • TypeForm: flags both consistently.
Probable cause: A constraint-solver asymmetry in baseline.

The only difference between the two calls is the second argument's type. cp is a 3-way union that includes RingElement* (the Er TypeVar's bound); cq is a 2-way union without it. Baseline pyright treats these very differently when solving for Er@dmp_mul_ground:

  • With cp (containing the bound): widens the solution, fails the dup[Unknown]
    invariance check on f → error.
  • With cq (no bound): solves cleanly, lets f's dup[Unknown] slide through
    Unknown-permissiveness → no error, and q ends up narrowed to
    dmp[Er@dmp_cancel] (the "clean" type seen at line 1976 in baseline).

TypeForm enabling makes the solver flag both cases uniformly. The q value at
line 1976 then carries dmp[Er@dmp_cancel | Unknown] — the Unknown that
baseline silently absorbed.

TypeForm enabling makes the solver flag both cases uniformly.

Thus the "new" error at line 1974 is not a regression. ✅

Summary of next steps

  • Investigate why the chosen overload in diophantine.py changed, to a choice that makes less sense to me that than the original choice

@davidfstr
Copy link
Copy Markdown
Author

davidfstr commented May 7, 2026

Holy moly. diophantine.py is complex. I think I'm going to disregard the remaining error deltas in sympy. sympy was already producing very many errors under pyright before this branch and now its producing very many but different errors after this branch. Not worth the investigation time: sympy was already broken at the start.

I've just pushed up commits integrating the outstanding feedback.

Next

So the next item (and possibly last) item I want to investigate is any performance regressions that this branch might introduce. I'll look at the psf/black codebase as issue #11008 did.

@rchiodo
Copy link
Copy Markdown
Collaborator

rchiodo commented May 7, 2026

GitHub cannot anchor PR review comments to unchanged lines in the diff. Falling back to a general PR comment for packages/pyright-internal/src/analyzer/typeEvaluator.ts:L5574.

Copilot generated:
expectedTypeWantsTypeForm uses doForEachSubtype which iterates all subtypes unconditionally (returns void, no early exit). The existing someSubtypes helper at typeUtils.ts:786 uses .some() with short-circuit semantics and returns boolean — exactly what this function needs.

Suggested fix:

function expectedTypeWantsTypeForm(expectedType: Type): boolean {
    return someSubtypes(expectedType, (subtype) =>
        isClassInstance(subtype) && ClassType.isBuiltIn(subtype, 'TypeForm')
    );
}

🔍 Structurally confirmed: doForEachSubtype signature at typeUtils.ts:771 returns void; someSubtypes at typeUtils.ts:786 returns boolean with .some().

[verified]

@@ -1743,11 +1744,15 @@ export function createTypeEvaluator(
};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot generated:
The wantsTypeForm guard is well-designed — it prevents expensive string-as-TypeForm interpretation for plain string literals. However, [unverified] — it's unclear whether inferenceContext propagates deeply enough through container literal evaluation. For example, in d: dict[str, TypeForm] = {"key": "int"}, does the TypeForm expected type reach the string "int" via inference context? A targeted test case for container scenarios (list[TypeForm], dict[str, TypeForm]) would confirm or refute this.

[verified]

@rchiodo
Copy link
Copy Markdown
Collaborator

rchiodo commented May 7, 2026

GitHub cannot anchor PR review comments to unchanged lines in the diff. Falling back to a general PR comment for packages/pyright-internal/src/analyzer/typeEvaluator.ts:L7659.

Copilot generated:
📍 packages/pyright-internal/src/analyzer/typeEvaluator.ts (all cloneWithTypeForm sites)

cloneWithTypeForm always performs two object spreads ({...type} + {...type.props}) even when the TypeForm value being set is identical to what's already there. Compare with cloneForCondition (types.ts:366) which short-circuits when both old and new values are undefined. Consider adding a similar short-circuit: if (type.props?.typeForm === typeForm) return type; — this would avoid unnecessary allocations on the ~19 call sites that are now unconditional. [unverified — impact depends on how often the no-op case occurs]

[verified]

@rchiodo
Copy link
Copy Markdown
Collaborator

rchiodo commented May 7, 2026

GitHub cannot anchor PR review comments to unchanged lines in the diff. Falling back to a general PR comment for packages/pyright-internal/src/analyzer/typeEvaluator.ts:L990.

Copilot generated:
-1044 (not in diff, but related)

All 7 TypeForm test files (typeForm1–7) still explicitly set enableExperimentalFeatures = true. The stated purpose of this PR is that TypeForm works by default, but no test validates this. Add at least one test that exercises TypeForm without enableExperimentalFeatures to prove the gate removal is effective.

[verified]

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.

4 participants