Skip to content

Enable TypeForm support by default#11412

Merged
rchiodo merged 14 commits into
microsoft:mainfrom
davidfstr:f/typeform_on
Jun 25, 2026
Merged

Enable TypeForm support by default#11412
rchiodo merged 14 commits into
microsoft:mainfrom
davidfstr:f/typeform_on

Conversation

@davidfstr

Copy link
Copy Markdown
Contributor

...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
Contributor Author

@microsoft-github-policy-service agree

Copy link
Copy Markdown
Contributor 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

This comment has been minimized.

@rchiodo

rchiodo commented May 1, 2026

Copy link
Copy Markdown
Collaborator

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
Contributor 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
Contributor 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

davidfstr commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

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

rchiodo commented May 7, 2026

Copy link
Copy Markdown
Collaborator

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]

Comment thread packages/pyright-internal/src/analyzer/typeEvaluator.ts
@rchiodo

rchiodo commented May 7, 2026

Copy link
Copy Markdown
Collaborator

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

rchiodo commented May 7, 2026

Copy link
Copy Markdown
Collaborator

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]

@davidfstr

Copy link
Copy Markdown
Contributor Author

Update: The following item is still my next item for this PR:

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.

Before I start the item, I will investigate/fix a performance regression on a similar PR in mypy. I expect I may be able to take learnings from that future fix and apply them to this PR.

@davidfstr

Copy link
Copy Markdown
Contributor Author

davidfstr and others added 3 commits June 19, 2026 10:24
Adds a dev-facing tool, adapted from mypy's misc/perf_compare.py, that
builds the production pyright CLI bundle at each of two or more commits and
times repeated runs over a fixed corpus, reporting robust winsorized paired
deltas vs a baseline. Supports wall or CPU-time metrics and single-file or
project corpora. Cached bundles live beside the script under
build/perfCompare/binaries/ (gitignored).

Also adds a brief pointer in CONTRIBUTING.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@davidfstr

Copy link
Copy Markdown
Contributor Author

I profiled this branch's changes against two targets ("corpuses"):

  1. the entire psf/black repo and
  2. psf/black's string-heavy profiling/dict_huge.py file (the testcase that motivated Slowdown when enabling enableExperimentalFeatures on string-heavy files #11008)

and found no significant performance regression in either.

Performance

build/perfCompare.py, single-process (pyright's default), paired per-round deltas. CPU time (lowest-variance), n=50.

Setup: Clone the corpus next to the pyright repo:

git clone --depth 1 --branch 26.5.1 https://github.com/psf/black ../black

psf/black's entire repo - branch base vs branch tip

python build/perfCompare.py --metric cpu --workers1 --warmup-runs 3 --num-runs 50 \
    --corpus ../black \
    3619ecd f05ea58
  • n=50: +19.4 ms ±29.1 (+0.31%), CI [−9.7, +48.5].

Not significant (CI includes 0).

psf/black's profiling/dict_huge.py - branch base vs branch tip

python build/perfCompare.py --metric cpu --workers1 --warmup-runs 3 --num-runs 50 \
    --corpus-file ../black/profiling/dict_huge.py \
    3619ecd f05ea58
  • n=50: +13.4 ms ±23.1 (+0.72%), CI [−9.7, +36.5].

Not significant (CI includes 0).

No significant regression on either corpus, including the string-heavy file that motivated #11008.

Next Step

I will review/integrate feedback from @rchiodo 's 4 comments above.

davidfstr and others added 2 commits June 25, 2026 07:29
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
davidfstr and others added 3 commits June 25, 2026 07:45
Review feedback suggested short-circuiting cloneWithTypeForm with
`if (type.props?.typeForm === typeForm) return type;` (mirroring
cloneForCondition) to avoid allocations on the now-unconditional call
sites. After measuring, this is not worth doing: the no-op case occurs in
only ~0.2% of calls, so the guard would add a branch to the hot path while
saving almost no allocations. No code change is made.

The reference-equality check rarely holds because nearly every call site
passes a freshly-allocated value (e.g. convertToInstance(type)), which is a
new object every time. A value-equality check (isTypeSame) would catch more
cases but costs far more than the allocation it would save, so it is a net
loss on the hot path.

Supporting data (instrumented cloneWithTypeForm over the full
typeEvaluator jest suite, 1,162 tests):

    cloneWithTypeForm calls : ~12,000
    `=== typeForm` would fire:     23  (~0.2%)
      of which both undefined:     22  (~0.18%)
      non-undefined ref match:      1

So the general `=== typeForm` guard buys essentially nothing beyond the
both-undefined case, which is itself only ~0.18% of calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses review feedback: the TypeForm tests set enableExperimentalFeatures
= true, so none validated this PR's core claim that TypeForm is enabled by
default. Remove the flag from the existing TypeForm1-8 tests so they all
exercise the default configuration and directly prove the
isTypeFormSupported gate removal is effective.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@davidfstr

Copy link
Copy Markdown
Contributor Author

@rchiodo I've added commits addressing all your outstanding feedback.

The PR branch is now pretty messy, with a lot of commits that should ultimately be squashed. Would you mind if I reordered/squashed the commits to get a clean history?

@rchiodo

rchiodo commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

If you want to squash that's fine. When we merge the PR, we'll also squash everything so it doesn't really matter which.

@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/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/ode/hypergeometric.py:247:67 - error: Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/ode/lie_group.py:619:61 - error: Operator "-" not supported for type "Unknown | Basic" (reportOperatorIssue)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:468:28 - error: Argument of type "Expr | Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
+   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:468:28 - error: Argument of type "Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
-     Type "Expr | Unknown | None" is not assignable to type "Expr"
+     Type "Unknown | None" is not assignable to type "Expr"
-   .../projects/sympy/sympy/solvers/ode/ode.py:1579:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1580:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1597:43 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1597:64 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1610:43 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1610:74 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1623:49 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1623:70 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1753:36 - error: Cannot access attribute "lhs" for class "Expr"
+     Attribute "lhs" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1753:55 - error: Cannot access attribute "rhs" for class "Expr"
+     Attribute "rhs" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1754:36 - error: Cannot access attribute "lhs" for class "Expr"
+     Attribute "lhs" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1755:17 - error: No overloads for "__setitem__" match the provided arguments (reportCallIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1755:17 - error: Argument of type "Equality | BooleanFalse | BooleanTrue | Unknown | Expr" cannot be assigned to parameter "value" of type "Equality | BooleanFalse | BooleanTrue" in function "__setitem__"
+     Type "Equality | BooleanFalse | BooleanTrue | Unknown | Expr" is not assignable to type "Equality | BooleanFalse | BooleanTrue"
+       Type "Expr" is not assignable to type "Equality | BooleanFalse | BooleanTrue"
+         "Expr" is not assignable to "Equality"
+         "Expr" is not assignable to "BooleanFalse"
+         "Expr" is not assignable to "BooleanTrue" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3442:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3442:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3443:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3443:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3444:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3445:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3446:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3458:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3459:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3460:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3500:5 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3500:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)

... (truncated 102 lines) ...

@rchiodo rchiodo merged commit ab5cb1a into microsoft:main Jun 25, 2026
22 of 23 checks passed
@rchiodo

rchiodo commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

@davidfstr, thanks for the PR

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