Repository: sympy/sympy
Pull request: Update old type annotation syntax to new TypeHints
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
Fixes #27845


#### Brief description of what is fixed or changed
 This PR modernizes type annotations in the SymPy codebase by:

- Convert "# type: X" comments to "variable: X" syntax.
- Replace typing aliases (tDict, tTuple, etc.) with built-in types.
- Add `from __future__ import annotations` for Python 3.8 compatibility.
- Remove redundant None initializations used only for typing.
- Convert Optional[X] to X | None and Union[A, B] to A | B.
- Added ' | None' to type for 'is_real: bool' in stats/rv.py::PSpace
- Updated 'Expr' to Expr in core/evalf.py

Only remaining typing tType:
- from typing import Sequence as tSequence
- In sympy/stats/stochastic_process_types.py

#### Other comments
Files modified:
```
$ git diff --name-only origin/master -- "*.py"
 sympy/__init__.py
sympy/abc.py
sympy/codegen/ast.py
sympy/codegen/fnodes.py
sympy/concrete/products.py
sympy/concrete/summations.py
sympy/core/evalf.py
sympy/core/exprtools.py
sympy/external/gmpy.py
sympy/external/pythonmpq.py
sympy/functions/combinatorial/numbers.py
sympy/functions/elementary/complexes.py
sympy/functions/elementary/exponential.py
sympy/functions/elementary/integers.py
sympy/functions/elementary/trigonometric.py
sympy/matrices/common.py
sympy/matrices/dense.py
sympy/matrices/matrixbase.py
sympy/parsing/sympy_parser.py
sympy/physics/units/definitions/dimension_definitions.py
sympy/physics/units/unitsystem.py
sympy/polys/domains/gaussiandomains.py
sympy/polys/matrices/domainmatrix.py
sympy/printing/printer.py
sympy/solvers/diophantine/diophantine.py
sympy/solvers/ode/single.py
sympy/stats/rv.py
sympy/stats/stochastic_process_types.py
sympy/tensor/array/expressions/array_expressions.py
sympy/tensor/array/expressions/from_array_to_matrix.py
sympy/tensor/tensor.py
```

#### Release Notes
<!-- BEGIN RELEASE NOTES -->
NO ENTRY
<!-- END RELEASE NOTES -->


Problem statement:
Remove old type annotation syntax
When the first type annotations were added to the sympy codebase it was not possible to use the current Python type annotation syntax so there are a mixture of things like type comments:
```
$ git grep '# type: int'
sympy/codegen/fnodes.py:    _decimals = None  # type: int
sympy/matrices/common.py:    rows = None  # type: int
sympy/matrices/common.py:    cols = None  # type: int
sympy/matrices/matrixbase.py:    rows = None  # type: int
sympy/matrices/matrixbase.py:    cols = None  # type: int
sympy/solvers/ode/single.py:    _order = None  # type: int
```
These don't need to be comments any more and can use the syntax like
```
cols: int
```
instead. I don't think that the assignment to `None` is needed either.

There are also uses of `tDict` etc from the typing module:
```
$ git grep 'tDict'
sympy/abc.py:from typing import Any, Dict as tDict
sympy/abc.py:ns: tDict[str, Any] = {}
sympy/abc.py:_clash1: tDict[str, Any] = {}
sympy/abc.py:_clash2: tDict[str, Any] = {}
sympy/core/evalf.py:from typing import Tuple as tTuple, Optional, Union as tUnion, Callable, List, Dict as tDict, Type, TYPE_CHECKING, \
sympy/core/evalf.py:OPT_DICT = tDict[str, Any]
...
```
Those can just use `dict` instead of `Dict` or `tDict`.

Generally `git grep typing` or `git grep '# type:'` shows these. It isn't necessary to use type comments or List, Dict or Tuple from the typing module any more. It is also not necessary to use Union because it can just be `A | B` etc. It is not necessary to use `Optional[A]` since that is just `A | None` and so on.

All of these things can be updated to use the current Python typing syntax.

Hints from issue discussion:
To use the new syntax it is necessary to add `from __future__ import annotations` at the top of the files for compatibility with Python 3.8.
@oscarbenjamin  I wouldn't mind working on this. What did you have in mind for this:

>  I don't think that the assignment to None is needed either.

Setting to an initial value of 0?

You can just use
```
cols: int
```
It isn't necessary to assign to it after PEP 526:
https://peps.python.org/pep-0526/
Subbmitted issue on #27848 regarding failing test case.

Commit message: Removed tTuple from core/evalf.py

Signed-off-by: Nicholas Laustrup <124007393+nicklaustrup@users.noreply.github.com>

Patch to review:
diff --git a/.mailmap b/.mailmap
index 5ba2c8bf0c07..d41eb5d62045 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1085,6 +1085,7 @@ Nguyen Truong Duy <truongduy134@yahoo.com>
 Nichita Utiu <nikita.utiu+github@gmail.com> <nichitautiu@nichitautiu-desktop.(none)>
 Nicholas Bollweg <nick.bollweg@gmail.com> <nbollweg@continuum.io>
 Nicholas J.S. Kinar <n.kinar@usask.ca>
+Nicholas Laustrup <124007393+nicklaustrup@users.noreply.github.com>
 Nick Curtis <nicholas.curtis@uconn.edu> <arghdos@gmail.com>
 Nick Harder <nharder@umich.edu>
 Nicko van Someren <nicko@nicko.org>
diff --git a/sympy/__init__.py b/sympy/__init__.py
index ddce8428879a..9c46110dd2bc 100644
--- a/sympy/__init__.py
+++ b/sympy/__init__.py
@@ -47,8 +47,10 @@ def __sympy_debug():
     else:
         raise RuntimeError("unrecognized value for SYMPY_DEBUG: %s" %
                            debug_str)
+# Fails py2 test if using type hinting
 SYMPY_DEBUG = __sympy_debug()  # type: bool
 
+
 from .core import (sympify, SympifyError, cacheit, Basic, Atom,
         preorder_traversal, S, Expr, AtomicExpr, UnevaluatedExpr, Symbol,
         Wild, Dummy, symbols, var, Number, Float, Rational, Integer,
diff --git a/sympy/abc.py b/sympy/abc.py
index 177e8902e78d..a6f7a4056e60 100644
--- a/sympy/abc.py
+++ b/sympy/abc.py
@@ -50,8 +50,8 @@
 pi(C, Q)
 
 """
-
-from typing import Any, Dict as tDict
+from __future__ import annotations
+from typing import Any
 
 import string
 
@@ -92,10 +92,10 @@
 _greek.remove("lambda")
 _greek.append("lamda")
 
-ns: tDict[str, Any] = {}
+ns: dict[str, Any] = {}
 exec('from sympy import *', ns)
-_clash1: tDict[str, Any] = {}
-_clash2: tDict[str, Any] = {}
+_clash1: dict[str, Any] = {}
+_clash2: dict[str, Any] = {}
 while ns:
     _k, _ = ns.popitem()
     if _k in _greek:
diff --git a/sympy/codegen/ast.py b/sympy/codegen/ast.py
index 0522a25eb301..dd774ca87c5c 100644
--- a/sympy/codegen/ast.py
+++ b/sympy/codegen/ast.py
@@ -511,7 +511,7 @@ class AugmentedAssignment(AssignmentBase):
        Symbol for binary operation being applied in the assignment, such as "+",
        "*", etc.
     """
-    binop = None  # type: str
+    binop: str | None
 
     @property
     def op(self):
diff --git a/sympy/codegen/fnodes.py b/sympy/codegen/fnodes.py
index 6d38a60e5f5c..8b972fcfe4d4 100644
--- a/sympy/codegen/fnodes.py
+++ b/sympy/codegen/fnodes.py
@@ -5,6 +5,7 @@
 as a SymPy function for symbolic manipulation.
 """
 
+from __future__ import annotations
 from sympy.codegen.ast import (
     Attribute, CodeBlock, FunctionCall, Node, none, String,
     Token, _mk_Tuple, Variable
@@ -620,8 +621,8 @@ class merge(F95Function):
 
 
 class _literal(Float):
-    _token = None  # type: str
-    _decimals = None  # type: int
+    _token: str
+    _decimals: int
 
     def _fcode(self, printer, *args, **kwargs):
         mantissa, sgnd_ex = ('%.{}e'.format(self._decimals) % self).split('e')
diff --git a/sympy/concrete/products.py b/sympy/concrete/products.py
index e6a51f105ab4..dd0355516835 100644
--- a/sympy/concrete/products.py
+++ b/sympy/concrete/products.py
@@ -1,4 +1,4 @@
-from typing import Tuple as tTuple
+from __future__ import annotations
 
 from .expr_with_intlimits import ExprWithIntLimits
 from .summations import Sum, summation, _dummy_with_inherited_properties_concrete
@@ -191,7 +191,7 @@ class Product(ExprWithIntLimits):
 
     __slots__ = ()
 
-    limits: tTuple[tTuple[Symbol, Expr, Expr]]
+    limits: tuple[tuple[Symbol, Expr, Expr]]
 
     def __new__(cls, function, *symbols, **assumptions):
         obj = ExprWithIntLimits.__new__(cls, function, *symbols, **assumptions)
diff --git a/sympy/concrete/summations.py b/sympy/concrete/summations.py
index 74bc2fa935ec..c94fa4339aa9 100644
--- a/sympy/concrete/summations.py
+++ b/sympy/concrete/summations.py
@@ -1,4 +1,4 @@
-from typing import Tuple as tTuple
+from __future__ import annotations
 
 from sympy.calculus.singularities import is_decreasing
 from sympy.calculus.accumulationbounds import AccumulationBounds
@@ -174,7 +174,7 @@ class Sum(AddWithLimits, ExprWithIntLimits):
 
     __slots__ = ()
 
-    limits: tTuple[tTuple[Symbol, Expr, Expr]]
+    limits: tuple[tuple[Symbol, Expr, Expr]]
 
     def __new__(cls, function, *symbols, **assumptions):
         obj = AddWithLimits.__new__(cls, function, *symbols, **assumptions)
diff --git a/sympy/core/evalf.py b/sympy/core/evalf.py
index 9c3615d37c92..f154ea247a79 100644
--- a/sympy/core/evalf.py
+++ b/sympy/core/evalf.py
@@ -3,8 +3,7 @@
 for mathematical functions.
 """
 from __future__ import annotations
-from typing import Tuple as tTuple, Optional, Union as tUnion, Callable, List, Dict as tDict, Type, TYPE_CHECKING, \
-    Any, overload
+from typing import Callable, TYPE_CHECKING, Any, overload, Type
 
 import math
 
@@ -78,7 +77,8 @@ class PrecisionExhausted(ArithmeticError):
 sign is 0 or 1 and bc should correspond to the number of bits used to
 represent the mantissa (man) in binary notation, e.g.
 """
-MPF_TUP = tTuple[int, int, int, int]  # mpf value tuple
+# Must be type Tuple for python 3.8 tests
+MPF_TUP = tuple[int, int, int, int]  # mpf value tuple
 
 """
 Explanation
@@ -108,10 +108,10 @@ class PrecisionExhausted(ArithmeticError):
 # 2. sometimes the result can't be zoo
 
 # type of the "options" parameter in internal evalf functions
-OPT_DICT = tDict[str, Any]
+OPT_DICT = dict[str, Any]
 
 
-def fastlog(x: Optional[MPF_TUP]) -> tUnion[int, Any]:
+def fastlog(x: MPF_TUP | None) -> int | Any:
     """Fast approximation of log2(x) for an mpf value tuple x.
 
     Explanation
@@ -147,7 +147,7 @@ def fastlog(x: Optional[MPF_TUP]) -> tUnion[int, Any]:
     return x[2] + x[3]
 
 
-def pure_complex(v: 'Expr', or_real=False) -> tuple['Number', 'Number'] | None:
+def pure_complex(v: Expr, or_real=False) -> tuple['Number', 'Number'] | None:
     """Return a and b if v matches a + I*b where b is not zero and
     a and b are Numbers, else None. If `or_real` is True then 0 will
     be returned for `b` if `v` is a real number.
@@ -178,17 +178,18 @@ def pure_complex(v: 'Expr', or_real=False) -> tuple['Number', 'Number'] | None:
 
 
 # I don't know what this is, see function scaled_zero below
-SCALED_ZERO_TUP = tTuple[List[int], int, int, int]
+SCALED_ZERO_TUP = tuple[list[int], int, int, int]
+
 
 
 @overload
 def scaled_zero(mag: SCALED_ZERO_TUP, sign=1) -> MPF_TUP:
     ...
 @overload
-def scaled_zero(mag: int, sign=1) -> tTuple[SCALED_ZERO_TUP, int]:
+def scaled_zero(mag: int, sign=1) -> tuple[SCALED_ZERO_TUP, int]:
     ...
-def scaled_zero(mag: tUnion[SCALED_ZERO_TUP, int], sign=1) -> \
-        tUnion[MPF_TUP, tTuple[SCALED_ZERO_TUP, int]]:
+def scaled_zero(mag: SCALED_ZERO_TUP | int, sign=1) -> \
+        MPF_TUP | tuple[SCALED_ZERO_TUP, int]:
     """Return an mpf representing a power of two with magnitude ``mag``
     and -1 for precision. Or, if ``mag`` is a scaled_zero tuple, then just
     remove the sign from within the list that it was initially wrapped
@@ -226,13 +227,13 @@ def scaled_zero(mag: tUnion[SCALED_ZERO_TUP, int], sign=1) -> \
         raise ValueError('scaled zero expects int or scaled_zero tuple.')
 
 
-def iszero(mpf: tUnion[MPF_TUP, SCALED_ZERO_TUP, None], scaled=False) -> Optional[bool]:
+def iszero(mpf: MPF_TUP | SCALED_ZERO_TUP | None, scaled=False) -> bool | None:
     if not scaled:
         return not mpf or not mpf[1] and not mpf[-1]
     return mpf and isinstance(mpf[0], list) and mpf[1] == mpf[-1] == 1
 
 
-def complex_accuracy(result: TMP_RES) -> tUnion[int, Any]:
+def complex_accuracy(result: TMP_RES) -> int | Any:
     """
     Returns relative accuracy of a complex number with given accuracies
     for the real and imaginary parts. The relative accuracy is defined
@@ -261,7 +262,7 @@ def complex_accuracy(result: TMP_RES) -> tUnion[int, Any]:
     return -relative_error
 
 
-def get_abs(expr: 'Expr', prec: int, options: OPT_DICT) -> TMP_RES:
+def get_abs(expr: Expr, prec: int, options: OPT_DICT) -> TMP_RES:
     result = evalf(expr, prec + 2, options)
     if result is S.ComplexInfinity:
         return finf, None, prec, None
@@ -283,7 +284,7 @@ def get_abs(expr: 'Expr', prec: int, options: OPT_DICT) -> TMP_RES:
         return None, None, None, None
 
 
-def get_complex_part(expr: 'Expr', no: int, prec: int, options: OPT_DICT) -> TMP_RES:
+def get_complex_part(expr: Expr, no: int, prec: int, options: OPT_DICT) -> TMP_RES:
     """no = 0 for real part, no = 1 for imaginary part"""
     workprec = prec
     i = 0
@@ -352,7 +353,7 @@ def chop_parts(value: TMP_RES, prec: int) -> TMP_RES:
     return re, im, re_acc, im_acc
 
 
-def check_target(expr: 'Expr', result: TMP_RES, prec: int):
+def check_target(expr: Expr, result: TMP_RES, prec: int):
     a = complex_accuracy(result)
     if a < prec:
         raise PrecisionExhausted("Failed to distinguish the expression: \n\n%s\n\n"
@@ -360,8 +361,8 @@ def check_target(expr: 'Expr', result: TMP_RES, prec: int):
             "a higher maxn for evalf" % (expr))
 
 
-def get_integer_part(expr: 'Expr', no: int, options: OPT_DICT, return_ints=False) -> \
-        tUnion[TMP_RES, tTuple[int, int]]:
+def get_integer_part(expr: Expr, no: int, options: OPT_DICT, return_ints=False) -> \
+        TMP_RES | tuple[int, int]:
     """
     With no = 1, computes ceiling(expr)
     With no = -1, computes floor(expr)
@@ -403,7 +404,7 @@ def get_integer_part(expr: 'Expr', no: int, options: OPT_DICT, return_ints=False
     # We can now easily find the nearest integer, but to find floor/ceil, we
     # must also calculate whether the difference to the nearest integer is
     # positive or negative (which may fail if very close).
-    def calc_part(re_im: 'Expr', nexpr: MPF_TUP):
+    def calc_part(re_im: Expr, nexpr: MPF_TUP):
         from .add import Add
         _, _, exponent, _ = nexpr
         is_int = exponent == 0
@@ -498,7 +499,7 @@ def evalf_integer(expr: 'Integer', prec: int, options: OPT_DICT) -> TMP_RES:
 
 
 def add_terms(terms: list, prec: int, target_prec: int) -> \
-        tTuple[tUnion[MPF_TUP, SCALED_ZERO_TUP, None], Optional[int]]:
+        tuple[MPF_TUP | SCALED_ZERO_TUP | None, int | None]:
     """
     Helper for evalf_add. Adds a list of (mpfval, accuracy) terms.
 
@@ -541,7 +542,7 @@ def add_terms(terms: list, prec: int, target_prec: int) -> \
 
     working_prec = 2*prec
     sum_man, sum_exp = 0, 0
-    absolute_err: List[int] = []
+    absolute_err: list[int] = []
 
     for x, accuracy in terms:
         sign, man, exp, bc = x
@@ -893,7 +894,7 @@ def evalf_exp(expr: 'exp', prec: int, options: OPT_DICT) -> TMP_RES:
     return evalf_pow(Pow(S.Exp1, expr.exp, evaluate=False), prec, options)
 
 
-def evalf_trig(v: 'Expr', prec: int, options: OPT_DICT) -> TMP_RES:
+def evalf_trig(v: Expr, prec: int, options: OPT_DICT) -> TMP_RES:
     """
     This function handles sin , cos and tan of complex arguments.
 
@@ -1031,7 +1032,7 @@ def evalf_subs(prec: int, subs: dict) -> dict:
     return newsubs
 
 
-def evalf_piecewise(expr: 'Expr', prec: int, options: OPT_DICT) -> TMP_RES:
+def evalf_piecewise(expr: Expr, prec: int, options: OPT_DICT) -> TMP_RES:
     from .numbers import Float, Integer
     if 'subs' in options:
         expr = expr.subs(evalf_subs(prec, options['subs']))
@@ -1058,7 +1059,7 @@ def evalf_alg_num(a: 'AlgebraicNumber', prec: int, options: OPT_DICT) -> TMP_RES
 #----------------------------------------------------------------------------#
 
 
-def as_mpmath(x: Any, prec: int, options: OPT_DICT) -> tUnion[mpc, mpf]:
+def as_mpmath(x: Any, prec: int, options: OPT_DICT) -> mpc | mpf:  # type: ignore
     from .numbers import Infinity, NegativeInfinity, Zero
     x = sympify(x)
     if isinstance(x, Zero) or x == 0.0:
@@ -1104,10 +1105,10 @@ def do_integral(expr: 'Integral', prec: int, options: OPT_DICT) -> TMP_RES:
         from .symbol import Wild
 
         have_part = [False, False]
-        max_real_term: tUnion[float, int] = MINUS_INF
-        max_imag_term: tUnion[float, int] = MINUS_INF
+        max_real_term: float | int = MINUS_INF
+        max_imag_term: float | int = MINUS_INF
 
-        def f(t: 'Expr') -> tUnion[mpc, mpf]:
+        def f(t: Expr) -> mpc | mpf:  # type: ignore
             nonlocal max_real_term, max_imag_term
             re, im, re_acc, im_acc = evalf(func, mp.prec, {'subs': {x: t}})
 
@@ -1142,8 +1143,8 @@ def f(t: 'Expr') -> tUnion[mpc, mpf]:
     options['maxprec'] = oldmaxprec
 
     if have_part[0]:
-        re: Optional[MPF_TUP] = result.real._mpf_
-        re_acc: Optional[int]
+        re: MPF_TUP | None = result.real._mpf_
+        re_acc: int | None
         if re == fzero:
             re_s, re_acc = scaled_zero(int(-max(prec, max_real_term, quadrature_error)))
             re = scaled_zero(re_s)  # handled ok in evalf_integral
@@ -1153,8 +1154,8 @@ def f(t: 'Expr') -> tUnion[mpc, mpf]:
         re, re_acc = None, None
 
     if have_part[1]:
-        im: Optional[MPF_TUP] = result.imag._mpf_
-        im_acc: Optional[int]
+        im: MPF_TUP | None = result.imag._mpf_
+        im_acc: int | None
         if im == fzero:
             im_s, im_acc = scaled_zero(int(-max(prec, max_imag_term, quadrature_error)))
             im = scaled_zero(im_s)  # handled ok in evalf_integral
@@ -1193,7 +1194,7 @@ def evalf_integral(expr: 'Integral', prec: int, options: OPT_DICT) -> TMP_RES:
     return result
 
 
-def check_convergence(numer: 'Expr', denom: 'Expr', n: 'Symbol') -> tTuple[int, Any, Any]:
+def check_convergence(numer: Expr, denom: Expr, n: 'Symbol') -> tuple[int, Any, Any]:
     """
     Returns
     =======
@@ -1235,7 +1236,7 @@ def check_convergence(numer: 'Expr', denom: 'Expr', n: 'Symbol') -> tTuple[int,
     return rate, constant, (qc - pc)/dpol.LC()
 
 
-def hypsum(expr: 'Expr', n: 'Symbol', start: int, prec: int) -> mpf:
+def hypsum(expr: Expr, n: 'Symbol', start: int, prec: int) -> mpf:
     """
     Sum a rapidly convergent infinite hypergeometric series with
     given general term, e.g. e = hypsum(1/factorial(n), n). The
@@ -1371,7 +1372,7 @@ def evalf_sum(expr: 'Sum', prec: int, options: OPT_DICT) -> TMP_RES:
 #                                                                            #
 #----------------------------------------------------------------------------#
 
-def evalf_symbol(x: 'Expr', prec: int, options: OPT_DICT) -> TMP_RES:
+def evalf_symbol(x: Expr, prec: int, options: OPT_DICT) -> TMP_RES:
     val = options['subs'][x]
     if isinstance(val, mpf):
         if not val:
@@ -1388,8 +1389,7 @@ def evalf_symbol(x: 'Expr', prec: int, options: OPT_DICT) -> TMP_RES:
         cache[x] = (v, prec)
         return v
 
-
-evalf_table: tDict[Type['Expr'], Callable[['Expr', int, OPT_DICT], TMP_RES]] = {}
+evalf_table: dict[Type[Expr], Callable[[Expr, int, OPT_DICT], TMP_RES]] = {}
 
 
 def _create_evalf_table():
@@ -1452,7 +1452,7 @@ def _create_evalf_table():
     }
 
 
-def evalf(x: 'Expr', prec: int, options: OPT_DICT) -> TMP_RES:
+def evalf(x: Expr, prec: int, options: OPT_DICT) -> TMP_RES:
     """
     Evaluate the ``Expr`` instance, ``x``
     to a binary precision of ``prec``. This
@@ -1560,7 +1560,7 @@ def quad_to_mpmath(q, ctx=None):
 class EvalfMixin:
     """Mixin class adding evalf capability."""
 
-    __slots__ = ()  # type: tTuple[str, ...]
+    __slots__: tuple[str, ...] = ()
 
     def evalf(self, n=15, subs=None, maxn=100, chop=False, strict=False, quad=None, verbose=False):
         """
@@ -1756,9 +1756,9 @@ def N(x, n=15, **options):
     return sympify(x, rational=True).evalf(n, **options)
 
 
-def _evalf_with_bounded_error(x: 'Expr', eps: 'Optional[Expr]' = None,
+def _evalf_with_bounded_error(x: Expr, eps: Expr | None = None,
                               m: int = 0,
-                              options: Optional[OPT_DICT] = None) -> TMP_RES:
+                              options: OPT_DICT | None = None) -> TMP_RES:
     """
     Evaluate *x* to within a bounded absolute error.
 
diff --git a/sympy/core/exprtools.py b/sympy/core/exprtools.py
index 07959c862191..0914ad170c00 100644
--- a/sympy/core/exprtools.py
+++ b/sympy/core/exprtools.py
@@ -21,7 +21,6 @@
         variations, iterable, is_sequence)
 
 from collections import defaultdict
-from typing import Tuple as tTuple
 
 
 _eps = Dummy(positive=True)
@@ -216,7 +215,7 @@ def _monotonic_sign(self):
             return rv.subs(_eps, 0)
 
 
-def decompose_power(expr: Expr) -> tTuple[Expr, int]:
+def decompose_power(expr: Expr) -> tuple[Expr, int]:
     """
     Decompose power into symbolic base and integer exponent.
 
@@ -260,7 +259,7 @@ def decompose_power(expr: Expr) -> tTuple[Expr, int]:
     return base, e
 
 
-def decompose_power_rat(expr: Expr) -> tTuple[Expr, Rational]:
+def decompose_power_rat(expr: Expr) -> tuple[Expr, Rational]:
     """
     Decompose power into symbolic base and rational exponent;
     if the exponent is not a Rational, then separate only the
diff --git a/sympy/external/gmpy.py b/sympy/external/gmpy.py
index b28da521a620..8554ce619cd7 100644
--- a/sympy/external/gmpy.py
+++ b/sympy/external/gmpy.py
@@ -1,7 +1,8 @@
+from __future__ import annotations
 import os
 from ctypes import c_long, sizeof
 from functools import reduce
-from typing import Tuple as tTuple, Type
+from typing import Type
 from warnings import warn
 
 from sympy.external import import_module
@@ -189,7 +190,7 @@ def _get_gmpy2(sympy_ground_types):
 # Type checkers are confused by what SYMPY_INTS is. There may be a better type
 # hint for this like Type[Integral] or something.
 #
-SYMPY_INTS: tTuple[Type, ...]
+SYMPY_INTS: tuple[Type, ...]
 
 if _SYMPY_GROUND_TYPES == 'gmpy':
 
diff --git a/sympy/external/pythonmpq.py b/sympy/external/pythonmpq.py
index b8efd18a40a7..4f2d102974e0 100644
--- a/sympy/external/pythonmpq.py
+++ b/sympy/external/pythonmpq.py
@@ -29,13 +29,13 @@
 are not using it when gmpy2 is installed either.
 """
 
-
+from __future__ import annotations
 import operator
 from math import gcd
 from decimal import Decimal
 from fractions import Fraction
 import sys
-from typing import Tuple as tTuple, Type
+from typing import Type
 
 
 # Used for __hash__
@@ -331,7 +331,7 @@ def __rtruediv__(self, other):
         else:
             return NotImplemented
 
-    _compatible_types: tTuple[Type, ...] = ()
+    _compatible_types: tuple[Type, ...] = ()
 
 #
 # These are the types that PythonMPQ will interoperate with for operations
diff --git a/sympy/functions/combinatorial/numbers.py b/sympy/functions/combinatorial/numbers.py
index 3903be6476d4..c0dfc518d4a6 100644
--- a/sympy/functions/combinatorial/numbers.py
+++ b/sympy/functions/combinatorial/numbers.py
@@ -9,7 +9,7 @@
 from __future__ import annotations
 from math import prod
 from collections import defaultdict
-from typing import Callable, Tuple as tTuple
+from typing import Callable
 
 from sympy.core import S, Symbol, Add, Dummy
 from sympy.core.cache import cacheit
@@ -517,7 +517,7 @@ class bernoulli(DefinedFunction):
 
     """
 
-    args: tTuple[Integer]
+    args: tuple[Integer]
 
     # Calculates B_n for positive even n
     @staticmethod
diff --git a/sympy/functions/elementary/complexes.py b/sympy/functions/elementary/complexes.py
index 27043838d73d..dd837e4e2420 100644
--- a/sympy/functions/elementary/complexes.py
+++ b/sympy/functions/elementary/complexes.py
@@ -1,4 +1,4 @@
-from typing import Tuple as tTuple
+from __future__ import annotations
 
 from sympy.core import S, Add, Mul, sympify, Symbol, Dummy, Basic
 from sympy.core.expr import Expr
@@ -59,7 +59,7 @@ class re(DefinedFunction):
     im
     """
 
-    args: tTuple[Expr]
+    args: tuple[Expr]
 
     is_extended_real = True
     unbranched = True  # implicitly works on the projection to C
@@ -181,7 +181,7 @@ class im(DefinedFunction):
     re
     """
 
-    args: tTuple[Expr]
+    args: tuple[Expr]
 
     is_extended_real = True
     unbranched = True  # implicitly works on the projection to C
@@ -501,7 +501,7 @@ class Abs(DefinedFunction):
     sign, conjugate
     """
 
-    args: tTuple[Expr]
+    args: tuple[Expr]
 
     is_extended_real = True
     is_extended_negative = False
diff --git a/sympy/functions/elementary/exponential.py b/sympy/functions/elementary/exponential.py
index 794574bb8dff..2bb0333cb34a 100644
--- a/sympy/functions/elementary/exponential.py
+++ b/sympy/functions/elementary/exponential.py
@@ -1,5 +1,5 @@
+from __future__ import annotations
 from itertools import product
-from typing import Tuple as tTuple
 
 from sympy.core.add import Add
 from sympy.core.cache import cacheit
@@ -638,7 +638,7 @@ class log(DefinedFunction):
 
     """
 
-    args: tTuple[Expr]
+    args: tuple[Expr]
 
     _singularities = (S.Zero, S.ComplexInfinity)
 
diff --git a/sympy/functions/elementary/integers.py b/sympy/functions/elementary/integers.py
index 2a49f1c647bf..d0b58d323991 100644
--- a/sympy/functions/elementary/integers.py
+++ b/sympy/functions/elementary/integers.py
@@ -1,4 +1,4 @@
-from typing import Tuple as tTuple
+from __future__ import annotations
 
 from sympy.core.basic import Basic
 from sympy.core.expr import Expr
@@ -21,7 +21,7 @@
 class RoundFunction(DefinedFunction):
     """Abstract base class for rounding functions."""
 
-    args: tTuple[Expr]
+    args: tuple[Expr]
 
     @classmethod
     def eval(cls, arg):
diff --git a/sympy/functions/elementary/trigonometric.py b/sympy/functions/elementary/trigonometric.py
index 60b9e7d5ea33..24e5db81f17a 100644
--- a/sympy/functions/elementary/trigonometric.py
+++ b/sympy/functions/elementary/trigonometric.py
@@ -1,4 +1,4 @@
-from typing import Tuple as tTuple, Union as tUnion
+from __future__ import annotations
 from sympy.core.add import Add
 from sympy.core.cache import cacheit
 from sympy.core.expr import Expr
@@ -165,7 +165,7 @@ def _peeloff_pi(arg):
     return arg, S.Zero
 
 
-def _pi_coeff(arg: Expr, cycles: int = 1) -> tUnion[Expr, None]:
+def _pi_coeff(arg: Expr, cycles: int = 1) -> Expr | None:
     r"""
     When arg is a Number times $\pi$ (e.g. $3\pi/2$) then return the Number
     normalized to be in the range $[0, 2]$, else `None`.
@@ -2028,7 +2028,7 @@ def _eval_is_real(self):
 
 class InverseTrigonometricFunction(DefinedFunction):
     """Base class for inverse trigonometric functions."""
-    _singularities = (S.One, S.NegativeOne, S.Zero, S.ComplexInfinity)  # type: tTuple[Expr, ...]
+    _singularities: tuple[Expr, ...] = (S.One, S.NegativeOne, S.Zero, S.ComplexInfinity)
 
     @staticmethod
     @cacheit
@@ -2620,7 +2620,7 @@ class atan(InverseTrigonometricFunction):
 
     """
 
-    args: tTuple[Expr]
+    args: tuple[Expr]
 
     _singularities = (S.ImaginaryUnit, -S.ImaginaryUnit)
 
diff --git a/sympy/matrices/common.py b/sympy/matrices/common.py
index f1f606a91644..bcb54726fe1a 100644
--- a/sympy/matrices/common.py
+++ b/sympy/matrices/common.py
@@ -8,7 +8,7 @@
 Importing anything else from this module is deprecated so anything here
 should either not be used or should be imported from somewhere else.
 """
-
+from __future__ import annotations
 from collections import defaultdict
 from collections.abc import Iterable
 from inspect import isfunction
@@ -127,8 +127,8 @@ def __instancecheck__(cls, instance):
 class MatrixRequired(metaclass=_MatrixDeprecatedMeta):
     """Deprecated mixin class for making matrix classes."""
 
-    rows = None  # type: int
-    cols = None  # type: int
+    rows: int
+    cols: int
     _simplify = None
 
     def __init_subclass__(cls, **kwargs):
@@ -3037,7 +3037,7 @@ class MatrixCommon(MatrixArithmetic, MatrixOperations, MatrixProperties,
                   MatrixSpecial, MatrixShaping):
     """All common matrix operations including basic arithmetic, shaping,
     and special matrices like `zeros`, and `eye`."""
-    _diff_wrt = True  # type: bool
+    _diff_wrt: bool = True
 
 
 class _MinimalMatrix:
diff --git a/sympy/matrices/dense.py b/sympy/matrices/dense.py
index d13e93211b46..98bf9931df54 100644
--- a/sympy/matrices/dense.py
+++ b/sympy/matrices/dense.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
 import random
 
 from sympy.core.basic import Basic
@@ -33,7 +34,7 @@ class DenseMatrix(RepMatrix):
     # Sparse classes should be implemented here.
     #
 
-    is_MatrixExpr = False  # type: bool
+    is_MatrixExpr: bool = False
 
     _op_priority = 10.01
     _class_priority = 4
diff --git a/sympy/matrices/matrixbase.py b/sympy/matrices/matrixbase.py
index 61d6e0db6d8d..a92572da1043 100644
--- a/sympy/matrices/matrixbase.py
+++ b/sympy/matrices/matrixbase.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
 from collections import defaultdict
 from collections.abc import Iterable
 from inspect import isfunction
@@ -110,9 +111,9 @@ class MatrixBase(Printable):
     zero = S.Zero
     one = S.One
 
-    _diff_wrt = True  # type: bool
-    rows = None  # type: int
-    cols = None  # type: int
+    _diff_wrt: bool = True
+    rows: int
+    cols: int
     _simplify = None
 
     @classmethod
diff --git a/sympy/parsing/sympy_parser.py b/sympy/parsing/sympy_parser.py
index 91b1cee05a39..9cfda9ce0f73 100644
--- a/sympy/parsing/sympy_parser.py
+++ b/sympy/parsing/sympy_parser.py
@@ -1,5 +1,5 @@
 """Transform a string with Python-like source code into SymPy expression. """
-
+from __future__ import annotations
 from tokenize import (generate_tokens, untokenize, TokenError,
     NUMBER, STRING, NAME, OP, ENDMARKER, ERRORTOKEN, NEWLINE)
 
@@ -10,8 +10,7 @@
 from io import StringIO
 import builtins
 import types
-from typing import Tuple as tTuple, Dict as tDict, Any, Callable, \
-    List, Optional, Union as tUnion
+from typing import Any, Callable
 from functools import reduce
 from sympy.assumptions.ask import AssumptionKeys
 from sympy.core.basic import Basic
@@ -23,9 +22,9 @@
 
 null = ''
 
-TOKEN = tTuple[int, str]
-DICT = tDict[str, Any]
-TRANS = Callable[[List[TOKEN], DICT, DICT], List[TOKEN]]
+TOKEN = tuple[int, str]
+DICT = dict[str, Any]
+TRANS = Callable[[list[TOKEN], DICT, DICT], list[TOKEN]]
 
 def _token_splittable(token_name: str) -> bool:
     """
@@ -56,7 +55,7 @@ def _token_callable(token: TOKEN, local_dict: DICT, global_dict: DICT, nextToken
     return callable(func) and not isinstance(func, Symbol)
 
 
-def _add_factorial_tokens(name: str, result: List[TOKEN]) -> List[TOKEN]:
+def _add_factorial_tokens(name: str, result: list[TOKEN]) -> list[TOKEN]:
     if result == [] or result[-1][1] == '(':
         raise TokenError()
 
@@ -84,7 +83,7 @@ def _add_factorial_tokens(name: str, result: List[TOKEN]) -> List[TOKEN]:
     return result
 
 
-class ParenthesisGroup(List[TOKEN]):
+class ParenthesisGroup(list[TOKEN]):
     """List of tokens representing an expression in parentheses."""
     pass
 
@@ -103,7 +102,7 @@ def __init__(self, function: TOKEN, args: ParenthesisGroup, exponent=None):
         self.exponent = exponent
         self.items = ['function', 'args', 'exponent']
 
-    def expand(self) -> List[TOKEN]:
+    def expand(self) -> list[TOKEN]:
         """Return a list of tokens representing the function"""
         return [self.function, *self.args]
 
@@ -115,8 +114,8 @@ def __repr__(self):
                                                 self.exponent)
 
 
-def _flatten(result: List[tUnion[TOKEN, AppliedFunction]]):
-    result2: List[TOKEN] = []
+def _flatten(result: list[TOKEN | AppliedFunction]):
+    result2: list[TOKEN] = []
     for tok in result:
         if isinstance(tok, AppliedFunction):
             result2.extend(tok.expand())
@@ -126,14 +125,14 @@ def _flatten(result: List[tUnion[TOKEN, AppliedFunction]]):
 
 
 def _group_parentheses(recursor: TRANS):
-    def _inner(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
+    def _inner(tokens: list[TOKEN], local_dict: DICT, global_dict: DICT):
         """Group tokens between parentheses with ParenthesisGroup.
 
         Also processes those tokens recursively.
 
         """
-        result: List[tUnion[TOKEN, ParenthesisGroup]] = []
-        stacks: List[ParenthesisGroup] = []
+        result: list[TOKEN | ParenthesisGroup] = []
+        stacks: list[ParenthesisGroup] = []
         stacklevel = 0
         for token in tokens:
             if token[0] == OP:
@@ -169,14 +168,14 @@ def _inner(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
     return _inner
 
 
-def _apply_functions(tokens: List[tUnion[TOKEN, ParenthesisGroup]], local_dict: DICT, global_dict: DICT):
+def _apply_functions(tokens: list[TOKEN | ParenthesisGroup], local_dict: DICT, global_dict: DICT):
     """Convert a NAME token + ParenthesisGroup into an AppliedFunction.
 
     Note that ParenthesisGroups, if not applied to any function, are
     converted back into lists of tokens.
 
     """
-    result: List[tUnion[TOKEN, AppliedFunction]] = []
+    result: list[TOKEN | AppliedFunction] = []
     symbol = None
     for tok in tokens:
         if isinstance(tok, ParenthesisGroup):
@@ -194,7 +193,7 @@ def _apply_functions(tokens: List[tUnion[TOKEN, ParenthesisGroup]], local_dict:
     return result
 
 
-def _implicit_multiplication(tokens: List[tUnion[TOKEN, AppliedFunction]], local_dict: DICT, global_dict: DICT):
+def _implicit_multiplication(tokens: list[TOKEN | AppliedFunction], local_dict: DICT, global_dict: DICT):
     """Implicitly adds '*' tokens.
 
     Cases:
@@ -210,7 +209,7 @@ def _implicit_multiplication(tokens: List[tUnion[TOKEN, AppliedFunction]], local
     - AppliedFunction next to an implicitly applied function ("sin(x)cos x")
 
     """
-    result: List[tUnion[TOKEN, AppliedFunction]] = []
+    result: list[TOKEN | AppliedFunction] = []
     skip = False
     for tok, nextTok in zip(tokens, tokens[1:]):
         result.append(tok)
@@ -259,9 +258,9 @@ def _implicit_multiplication(tokens: List[tUnion[TOKEN, AppliedFunction]], local
     return result
 
 
-def _implicit_application(tokens: List[tUnion[TOKEN, AppliedFunction]], local_dict: DICT, global_dict: DICT):
+def _implicit_application(tokens: list[TOKEN | AppliedFunction], local_dict: DICT, global_dict: DICT):
     """Adds parentheses as needed after functions."""
-    result: List[tUnion[TOKEN, AppliedFunction]] = []
+    result: list[TOKEN | AppliedFunction] = []
     appendParen = 0  # number of closing parentheses to add
     skip = 0  # number of tokens to delay before adding a ')' (to
               # capture **, ^, etc.)
@@ -310,7 +309,7 @@ def _implicit_application(tokens: List[tUnion[TOKEN, AppliedFunction]], local_di
     return result
 
 
-def function_exponentiation(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
+def function_exponentiation(tokens: list[TOKEN], local_dict: DICT, global_dict: DICT):
     """Allows functions to be exponentiated, e.g. ``cos**2(x)``.
 
     Examples
@@ -322,8 +321,8 @@ def function_exponentiation(tokens: List[TOKEN], local_dict: DICT, global_dict:
     >>> parse_expr('sin**4(x)', transformations=transformations)
     sin(x)**4
     """
-    result: List[TOKEN] = []
-    exponent: List[TOKEN] = []
+    result: list[TOKEN] = []
+    exponent: list[TOKEN] = []
     consuming_exponent = False
     level = 0
     for tok, nextTok in zip(tokens, tokens[1:]):
@@ -384,8 +383,8 @@ def split_symbols_custom(predicate: Callable[[str], bool]):
     ... (transformation, implicit_multiplication))
     unsplittable
     """
-    def _split_symbols(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
-        result: List[TOKEN] = []
+    def _split_symbols(tokens: list[TOKEN], local_dict: DICT, global_dict: DICT):
+        result: list[TOKEN] = []
         split = False
         split_previous=False
 
@@ -452,8 +451,8 @@ def _split_symbols(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
 split_symbols = split_symbols_custom(_token_splittable)
 
 
-def implicit_multiplication(tokens: List[TOKEN], local_dict: DICT,
-                            global_dict: DICT) -> List[TOKEN]:
+def implicit_multiplication(tokens: list[TOKEN], local_dict: DICT,
+                            global_dict: DICT) -> list[TOKEN]:
     """Makes the multiplication operator optional in most cases.
 
     Use this before :func:`implicit_application`, otherwise expressions like
@@ -476,8 +475,8 @@ def implicit_multiplication(tokens: List[TOKEN], local_dict: DICT,
     return result
 
 
-def implicit_application(tokens: List[TOKEN], local_dict: DICT,
-                         global_dict: DICT) -> List[TOKEN]:
+def implicit_application(tokens: list[TOKEN], local_dict: DICT,
+                         global_dict: DICT) -> list[TOKEN]:
     """Makes parentheses optional in some cases for function calls.
 
     Use this after :func:`implicit_multiplication`, otherwise expressions
@@ -500,8 +499,8 @@ def implicit_application(tokens: List[TOKEN], local_dict: DICT,
     return result
 
 
-def implicit_multiplication_application(result: List[TOKEN], local_dict: DICT,
-                                        global_dict: DICT) -> List[TOKEN]:
+def implicit_multiplication_application(result: list[TOKEN], local_dict: DICT,
+                                        global_dict: DICT) -> list[TOKEN]:
     """Allows a slightly relaxed syntax.
 
     - Parentheses for single-argument method calls are optional.
@@ -531,9 +530,9 @@ def implicit_multiplication_application(result: List[TOKEN], local_dict: DICT,
     return result
 
 
-def auto_symbol(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
+def auto_symbol(tokens: list[TOKEN], local_dict: DICT, global_dict: DICT):
     """Inserts calls to ``Symbol``/``Function`` for undefined variables."""
-    result: List[TOKEN] = []
+    result: list[TOKEN] = []
     prevTok = (-1, '')
 
     tokens.append((-1, ''))  # so zip traverses all tokens
@@ -582,13 +581,13 @@ def auto_symbol(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
     return result
 
 
-def lambda_notation(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
+def lambda_notation(tokens: list[TOKEN], local_dict: DICT, global_dict: DICT):
     """Substitutes "lambda" with its SymPy equivalent Lambda().
     However, the conversion does not take place if only "lambda"
     is passed because that is a syntax error.
 
     """
-    result: List[TOKEN] = []
+    result: list[TOKEN] = []
     flag = False
     toknum, tokval = tokens[0]
     tokLen = len(tokens)
@@ -622,9 +621,9 @@ def lambda_notation(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
     return result
 
 
-def factorial_notation(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
+def factorial_notation(tokens: list[TOKEN], local_dict: DICT, global_dict: DICT):
     """Allows standard notation for factorial."""
-    result: List[TOKEN] = []
+    result: list[TOKEN] = []
     nfactorial = 0
     for toknum, tokval in tokens:
         if toknum == OP and tokval == "!":
@@ -649,9 +648,9 @@ def factorial_notation(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT)
     return result
 
 
-def convert_xor(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
+def convert_xor(tokens: list[TOKEN], local_dict: DICT, global_dict: DICT):
     """Treats XOR, ``^``, as exponentiation, ``**``."""
-    result: List[TOKEN] = []
+    result: list[TOKEN] = []
     for toknum, tokval in tokens:
         if toknum == OP:
             if tokval == '^':
@@ -664,20 +663,20 @@ def convert_xor(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
     return result
 
 
-def repeated_decimals(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
+def repeated_decimals(tokens: list[TOKEN], local_dict: DICT, global_dict: DICT):
     """
     Allows 0.2[1] notation to represent the repeated decimal 0.2111... (19/90)
 
     Run this before auto_number.
 
     """
-    result: List[TOKEN] = []
+    result: list[TOKEN] = []
 
     def is_digit(s):
         return all(i in '0123456789_' for i in s)
 
     # num will running match any DECIMAL [ INTEGER ]
-    num: List[TOKEN] = []
+    num: list[TOKEN] = []
     for toknum, tokval in tokens:
         if toknum == NUMBER:
             if (not num and '.' in tokval and 'e' not in tokval.lower() and
@@ -752,7 +751,7 @@ def is_digit(s):
     return result
 
 
-def auto_number(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
+def auto_number(tokens: list[TOKEN], local_dict: DICT, global_dict: DICT):
     """
     Converts numeric literals to use SymPy equivalents.
 
@@ -760,7 +759,7 @@ def auto_number(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
     literals use ``Float``.
 
     """
-    result: List[TOKEN] = []
+    result: list[TOKEN] = []
 
     for toknum, tokval in tokens:
         if toknum == NUMBER:
@@ -786,9 +785,9 @@ def auto_number(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
     return result
 
 
-def rationalize(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
+def rationalize(tokens: list[TOKEN], local_dict: DICT, global_dict: DICT):
     """Converts floats into ``Rational``. Run AFTER ``auto_number``."""
-    result: List[TOKEN] = []
+    result: list[TOKEN] = []
     passed_float = False
     for toknum, tokval in tokens:
         if toknum == NAME:
@@ -805,7 +804,7 @@ def rationalize(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
     return result
 
 
-def _transform_equals_sign(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
+def _transform_equals_sign(tokens: list[TOKEN], local_dict: DICT, global_dict: DICT):
     """Transforms the equals sign ``=`` to instances of Eq.
 
     This is a helper function for ``convert_equals_signs``.
@@ -819,7 +818,7 @@ def _transform_equals_sign(tokens: List[TOKEN], local_dict: DICT, global_dict: D
     This does not deal with function arguments yet.
 
     """
-    result: List[TOKEN] = []
+    result: list[TOKEN] = []
     if (OP, "=") in tokens:
         result.append((NAME, "Eq"))
         result.append((OP, "("))
@@ -834,8 +833,8 @@ def _transform_equals_sign(tokens: List[TOKEN], local_dict: DICT, global_dict: D
     return result
 
 
-def convert_equals_signs(tokens: List[TOKEN], local_dict: DICT,
-                         global_dict: DICT) -> List[TOKEN]:
+def convert_equals_signs(tokens: list[TOKEN], local_dict: DICT,
+                         global_dict: DICT) -> list[TOKEN]:
     """ Transforms all the equals signs ``=`` to instances of Eq.
 
     Parses the equals signs in the expression and replaces them with
@@ -873,13 +872,13 @@ def convert_equals_signs(tokens: List[TOKEN], local_dict: DICT,
 #: Standard transformations for :func:`parse_expr`.
 #: Inserts calls to :class:`~.Symbol`, :class:`~.Integer`, and other SymPy
 #: datatypes and allows the use of standard factorial notation (e.g. ``x!``).
-standard_transformations: tTuple[TRANS, ...] \
+standard_transformations: tuple[TRANS, ...] \
     = (lambda_notation, auto_symbol, repeated_decimals, auto_number,
        factorial_notation)
 
 
 def stringify_expr(s: str, local_dict: DICT, global_dict: DICT,
-        transformations: tTuple[TRANS, ...]) -> str:
+        transformations: tuple[TRANS, ...]) -> str:
     """
     Converts the string ``s`` to Python code, in ``local_dict``
 
@@ -908,10 +907,10 @@ def eval_expr(code, local_dict: DICT, global_dict: DICT):
     return expr
 
 
-def parse_expr(s: str, local_dict: Optional[DICT] = None,
-               transformations: tUnion[tTuple[TRANS, ...], str] \
+def parse_expr(s: str, local_dict: DICT | None = None,
+               transformations: tuple[TRANS, ...] | str \
                    = standard_transformations,
-               global_dict: Optional[DICT] = None, evaluate=True):
+               global_dict: DICT | None = None, evaluate=True):
     """Converts the string ``s`` to a SymPy expression, in ``local_dict``.
 
     .. warning::
diff --git a/sympy/physics/units/definitions/dimension_definitions.py b/sympy/physics/units/definitions/dimension_definitions.py
index c9950d5855ee..b2b5f1dee01f 100644
--- a/sympy/physics/units/definitions/dimension_definitions.py
+++ b/sympy/physics/units/definitions/dimension_definitions.py
@@ -1,7 +1,7 @@
 from sympy.physics.units import Dimension
 
 
-angle = Dimension(name="angle")  # type: Dimension
+angle: Dimension = Dimension(name="angle")
 
 # base dimensions (MKS)
 length = Dimension(name="length", symbol="L")
@@ -9,12 +9,12 @@
 time = Dimension(name="time", symbol="T")
 
 # base dimensions (MKSA not in MKS)
-current = Dimension(name='current', symbol='I')  # type: Dimension
+current: Dimension = Dimension(name='current', symbol='I')
 
 # other base dimensions:
-temperature = Dimension("temperature", "T")  # type: Dimension
-amount_of_substance = Dimension("amount_of_substance")  # type: Dimension
-luminous_intensity = Dimension("luminous_intensity")  # type: Dimension
+temperature: Dimension = Dimension("temperature", "T")
+amount_of_substance: Dimension = Dimension("amount_of_substance")
+luminous_intensity: Dimension = Dimension("luminous_intensity")
 
 # derived dimensions (MKS)
 velocity = Dimension(name="velocity")
@@ -30,14 +30,14 @@
 volume = Dimension("volume")
 
 # derived dimensions (MKSA not in MKS)
-voltage = Dimension(name='voltage', symbol='U')  # type: Dimension
-impedance = Dimension(name='impedance', symbol='Z')  # type: Dimension
-conductance = Dimension(name='conductance', symbol='G')  # type: Dimension
-capacitance = Dimension(name='capacitance')  # type: Dimension
-inductance = Dimension(name='inductance')  # type: Dimension
-charge = Dimension(name='charge', symbol='Q')  # type: Dimension
-magnetic_density = Dimension(name='magnetic_density', symbol='B')  # type: Dimension
-magnetic_flux = Dimension(name='magnetic_flux')  # type: Dimension
+voltage: Dimension = Dimension(name='voltage', symbol='U')
+impedance: Dimension = Dimension(name='impedance', symbol='Z')
+conductance: Dimension = Dimension(name='conductance', symbol='G')
+capacitance: Dimension = Dimension(name='capacitance')
+inductance: Dimension = Dimension(name='inductance')
+charge: Dimension = Dimension(name='charge', symbol='Q')
+magnetic_density: Dimension = Dimension(name='magnetic_density', symbol='B')
+magnetic_flux: Dimension = Dimension(name='magnetic_flux')
 
 # Dimensions in information theory:
-information = Dimension(name='information')  # type: Dimension
+information: Dimension = Dimension(name='information')
diff --git a/sympy/physics/units/unitsystem.py b/sympy/physics/units/unitsystem.py
index 5705c821c217..795f8026e9df 100644
--- a/sympy/physics/units/unitsystem.py
+++ b/sympy/physics/units/unitsystem.py
@@ -1,8 +1,7 @@
 """
 Unit system for physical quantities; include definition of constants.
 """
-
-from typing import Dict as tDict, Set as tSet
+from __future__ import annotations
 
 from sympy.core.add import Add
 from sympy.core.function import (Derivative, Function)
@@ -25,9 +24,9 @@ class UnitSystem(_QuantityMapper):
     It is much better if all base units have a symbol.
     """
 
-    _unit_systems = {}  # type: tDict[str, UnitSystem]
+    _unit_systems: dict[str, UnitSystem] = {}
 
-    def __init__(self, base_units, units=(), name="", descr="", dimension_system=None, derived_units: tDict[Dimension, Quantity]={}):
+    def __init__(self, base_units, units=(), name="", descr="", dimension_system=None, derived_units: dict[Dimension, Quantity]={}):
 
         UnitSystem._unit_systems[name] = self
 
@@ -59,7 +58,7 @@ def __str__(self):
     def __repr__(self):
         return '<UnitSystem: %s>' % repr(self._base_units)
 
-    def extend(self, base, units=(), name="", description="", dimension_system=None, derived_units: tDict[Dimension, Quantity]={}):
+    def extend(self, base, units=(), name="", description="", dimension_system=None, derived_units: dict[Dimension, Quantity]={}):
         """Extend the current system into a new one.
 
         Take the base and normal units of the current system to merge
@@ -124,7 +123,7 @@ def is_consistent(self):
         return self.get_dimension_system().is_consistent
 
     @property
-    def derived_units(self) -> tDict[Dimension, Quantity]:
+    def derived_units(self) -> dict[Dimension, Quantity]:
         return self._derived_units
 
     def get_dimensional_expr(self, expr):
@@ -198,7 +197,7 @@ def _collect_factor_and_dimension(self, expr):
         else:
             return expr, Dimension(1)
 
-    def get_units_non_prefixed(self) -> tSet[Quantity]:
+    def get_units_non_prefixed(self) -> set[Quantity]:
         """
         Return the units of the system that do not have a prefix.
         """
diff --git a/sympy/polys/domains/gaussiandomains.py b/sympy/polys/domains/gaussiandomains.py
index c8e559855d36..a96bed78e294 100644
--- a/sympy/polys/domains/gaussiandomains.py
+++ b/sympy/polys/domains/gaussiandomains.py
@@ -1,5 +1,6 @@
 """Domains of Gaussian type."""
 
+from __future__ import annotations
 from sympy.core.numbers import I
 from sympy.polys.polyclasses import DMP
 from sympy.polys.polyerrors import CoercionFailed
@@ -244,7 +245,7 @@ def __divmod__(self, other):
 
 class GaussianDomain():
     """Base class for Gaussian domains."""
-    dom = None  # type: Domain
+    dom: Domain
 
     is_Numerical = True
     is_Exact = True
diff --git a/sympy/polys/matrices/domainmatrix.py b/sympy/polys/matrices/domainmatrix.py
index 0c64e0a022e6..627835eca93b 100644
--- a/sympy/polys/matrices/domainmatrix.py
+++ b/sympy/polys/matrices/domainmatrix.py
@@ -9,9 +9,9 @@
 as unifying matrices with different domains.
 
 """
+from __future__ import annotations
 from collections import Counter
 from functools import reduce
-from typing import Union as tUnion, Tuple as tTuple
 
 from sympy.external.gmpy import GROUND_TYPES
 from sympy.utilities.decorator import doctest_depends_on
@@ -131,8 +131,8 @@ class DomainMatrix:
     Poly
 
     """
-    rep: tUnion[SDM, DDM, DFM]
-    shape: tTuple[int, int]
+    rep: SDM | DDM | DFM
+    shape: tuple[int, int]
     domain: Domain
 
     def __new__(cls, rows, shape, domain, *, fmt=None):
diff --git a/sympy/printing/printer.py b/sympy/printing/printer.py
index ec4c2eaa99d8..0c0a6970920c 100644
--- a/sympy/printing/printer.py
+++ b/sympy/printing/printer.py
@@ -245,6 +245,7 @@ class Printer:
 
     _default_settings: dict[str, Any] = {}
 
+    # must be initialized to pass tests and cannot be set to '| None' to pass mypy
     printmethod = None  # type: str
 
     @classmethod
diff --git a/sympy/solvers/diophantine/diophantine.py b/sympy/solvers/diophantine/diophantine.py
index f5748439fe87..4fe60ce05934 100644
--- a/sympy/solvers/diophantine/diophantine.py
+++ b/sympy/solvers/diophantine/diophantine.py
@@ -167,7 +167,7 @@ class DiophantineEquationType:
     dimension :
         The number of symbols being solved for
     """
-    name = None  # type: str
+    name: str
 
     def __init__(self, equation, free_symbols=None):
         self.equation = _sympify(equation).expand(force=True)
diff --git a/sympy/solvers/ode/single.py b/sympy/solvers/ode/single.py
index eec608f602bb..c4829acf4129 100644
--- a/sympy/solvers/ode/single.py
+++ b/sympy/solvers/ode/single.py
@@ -70,12 +70,12 @@ class SingleODEProblem:
     """
 
     # Instance attributes:
-    eq = None  # type: Expr
-    func = None  # type: AppliedUndef
-    sym = None  # type: Symbol
-    _order = None  # type: int
-    _eq_expanded = None  # type: Expr
-    _eq_preprocessed = None  # type: Expr
+    eq: Expr
+    func: AppliedUndef
+    sym: Symbol
+    _order: int
+    _eq_expanded: Expr
+    _eq_preprocessed: Expr
     _eq_high_order_free = None
 
     def __init__(self, eq, func, sym, prep=True, **kwargs):
@@ -255,7 +255,7 @@ class SingleODESolver:
     has_integral: ClassVar[bool]
 
     # The ODE to be solved
-    ode_problem = None  # type: SingleODEProblem
+    ode_problem: SingleODEProblem
 
     # Cache whether or not the equation has matched the method
     _matched: bool | None = None
diff --git a/sympy/stats/rv.py b/sympy/stats/rv.py
index 67d9d7a1b5aa..75ab54deb551 100644
--- a/sympy/stats/rv.py
+++ b/sympy/stats/rv.py
@@ -196,10 +196,10 @@ class PSpace(Basic):
     sympy.stats.frv.FinitePSpace
     """
 
-    is_Finite = None  # type: bool
-    is_Continuous = None  # type: bool
-    is_Discrete = None  # type: bool
-    is_real = None  # type: bool
+    is_Finite: bool | None = None  # Fails test if not set to None
+    is_Continuous: bool | None = None  # Fails test if not set to None
+    is_Discrete: bool | None = None  # Fails test if not set to None
+    is_real: bool | None
 
     @property
     def domain(self):
diff --git a/sympy/stats/stochastic_process_types.py b/sympy/stats/stochastic_process_types.py
index 46721829caa1..7387cd3dbcf6 100644
--- a/sympy/stats/stochastic_process_types.py
+++ b/sympy/stats/stochastic_process_types.py
@@ -1,7 +1,7 @@
+from __future__ import annotations
 import random
 import itertools
-from typing import (Sequence as tSequence, Union as tUnion, List as tList,
-        Tuple as tTuple, Set as tSet)
+from typing import Sequence as tSequence
 from sympy.concrete.summations import Sum
 from sympy.core.add import Add
 from sympy.core.basic import Basic
@@ -101,12 +101,12 @@ def _set_converter(itr):
         raise TypeError("%s is not an instance of list/tuple/set."%(itr))
     return itr
 
-def _state_converter(itr: tSequence) -> tUnion[Tuple, Range]:
+def _state_converter(itr: tSequence) -> Tuple | Range:
     """
     Helper function for converting list/tuple/set/Range/Tuple/FiniteSet
     to tuple/Range.
     """
-    itr_ret: tUnion[Tuple, Range]
+    itr_ret: Tuple | Range
 
     if isinstance(itr, (Tuple, set, FiniteSet)):
         itr_ret = Tuple(*(sympify(i) if isinstance(i, str) else i for i in itr))
@@ -192,7 +192,7 @@ def symbol(self):
         return self.args[0]
 
     @property
-    def state_space(self) -> tUnion[FiniteSet, Range]:
+    def state_space(self) -> FiniteSet | Range:
         if not isinstance(self.args[1], (FiniteSet, Range)):
             assert isinstance(self.args[1], Tuple)
             return FiniteSet(*self.args[1])
@@ -376,7 +376,7 @@ class MarkovProcess(StochasticProcess):
     """
 
     @property
-    def number_of_states(self) -> tUnion[Integer, Symbol]:
+    def number_of_states(self) -> Integer | Symbol:
         """
         The number of states in the Markov Chain.
         """
@@ -961,7 +961,7 @@ def transition_probabilities(self):
         """
         return self.args[2]
 
-    def communication_classes(self) -> tList[tTuple[tList[Basic], Boolean, Integer]]:
+    def communication_classes(self) -> list[tuple[list[Basic], Boolean, Integer]]:
         """
         Returns the list of communication classes that partition
         the states of the markov chain.
@@ -1054,7 +1054,7 @@ def communication_classes(self) -> tList[tTuple[tList[Basic], Boolean, Integer]]
             # end recurrent check
 
             # begin breadth-first search
-            non_tree_edge_values: tSet[int] = set()
+            non_tree_edge_values: set[int] = set()
             visited = {class_[0]}
             newly_visited = {class_[0]}
             level = {class_[0]: 0}
@@ -1173,7 +1173,7 @@ def is_absorbing_chain(self):
         r = A.shape[0]
         return And(r > 0, A == Identity(r).as_explicit())
 
-    def stationary_distribution(self, condition_set=False) -> tUnion[ImmutableMatrix, ConditionSet, Lambda]:
+    def stationary_distribution(self, condition_set=False) -> ImmutableMatrix | ConditionSet | Lambda:
         r"""
         The stationary distribution is any row vector, p, that solves p = pP,
         is row stochastic and each element in p must be nonnegative.
@@ -1273,7 +1273,7 @@ def limiting_distribution(self):
         """
         return self.fixed_row_vector()
 
-    def decompose(self) -> tTuple[tList[Basic], ImmutableMatrix, ImmutableMatrix, ImmutableMatrix]:
+    def decompose(self) -> tuple[list[Basic], ImmutableMatrix, ImmutableMatrix, ImmutableMatrix]:
         """
         Decomposes the transition matrix into submatrices with
         special properties.
@@ -1375,7 +1375,7 @@ def decompose(self) -> tTuple[tList[Basic], ImmutableMatrix, ImmutableMatrix, Im
 
         return states, A.as_immutable(), B.as_immutable(), C.as_immutable()
 
-    def canonical_form(self) -> tTuple[tList[Basic], ImmutableMatrix]:
+    def canonical_form(self) -> tuple[list[Basic], ImmutableMatrix]:
         """
         Reorders the one-step transition matrix
         so that recurrent states appear first and transient
diff --git a/sympy/tensor/array/expressions/array_expressions.py b/sympy/tensor/array/expressions/array_expressions.py
index a26e6fc4c40a..9c80a3480986 100644
--- a/sympy/tensor/array/expressions/array_expressions.py
+++ b/sympy/tensor/array/expressions/array_expressions.py
@@ -1,10 +1,10 @@
+from __future__ import annotations
 import collections.abc
 import operator
 from collections import defaultdict, Counter
 from functools import reduce
 import itertools
 from itertools import accumulate
-from typing import Optional, List, Tuple as tTuple
 
 import typing
 
@@ -37,7 +37,7 @@
 
 
 class _ArrayExpr(Expr):
-    shape: tTuple[Expr, ...]
+    shape: tuple[Expr, ...]
 
     def __getitem__(self, item):
         if not isinstance(item, collections.abc.Iterable):
@@ -1588,9 +1588,9 @@ class _ArgE:
     the second index is contracted to the 4th (i.e. number ``3``) group of the
     array contraction object.
     """
-    indices: List[Optional[int]]
+    indices: list[int | None]
 
-    def __init__(self, element, indices: Optional[List[Optional[int]]] = None):
+    def __init__(self, element, indices: list[int | None] | None = None):
         self.element = element
         if indices is None:
             self.indices = [None for i in range(get_rank(element))]
@@ -1641,8 +1641,8 @@ class _EditArrayContraction:
     def __init__(self, base_array: typing.Union[ArrayContraction, ArrayDiagonal, ArrayTensorProduct]):
 
         expr: Basic
-        diagonalized: tTuple[tTuple[int, ...], ...]
-        contraction_indices: List[tTuple[int]]
+        diagonalized: tuple[tuple[int, ...], ...]
+        contraction_indices: list[tuple[int]]
         if isinstance(base_array, ArrayContraction):
             mapping = _get_mapping_from_subranks(base_array.subranks)
             expr = base_array.expr
@@ -1678,14 +1678,14 @@ def __init__(self, base_array: typing.Union[ArrayContraction, ArrayDiagonal, Arr
         else:
             args = [expr]
 
-        args_with_ind: List[_ArgE] = [_ArgE(arg) for arg in args]
+        args_with_ind: list[_ArgE] = [_ArgE(arg) for arg in args]
         for i, contraction_tuple in enumerate(contraction_indices):
             for j in contraction_tuple:
                 arg_pos, rel_pos = mapping[j]
                 args_with_ind[arg_pos].indices[rel_pos] = i
-        self.args_with_ind: List[_ArgE] = args_with_ind
+        self.args_with_ind: list[_ArgE] = args_with_ind
         self.number_of_contraction_indices: int = len(contraction_indices)
-        self._track_permutation: Optional[List[List[int]]] = None
+        self._track_permutation: list[list[int]] | None = None
 
         mapping = _get_mapping_from_subranks(base_array.subranks)
 
@@ -1794,8 +1794,8 @@ def to_array_contraction(self):
         expr3 = _permute_dims(expr2, permutation)
         return expr3
 
-    def get_contraction_indices(self) -> List[List[int]]:
-        contraction_indices: List[List[int]] = [[] for i in range(self.number_of_contraction_indices)]
+    def get_contraction_indices(self) -> list[list[int]]:
+        contraction_indices: list[list[int]] = [[] for i in range(self.number_of_contraction_indices)]
         current_position: int = 0
         for arg_with_ind in self.args_with_ind:
             for j in arg_with_ind.indices:
@@ -1804,18 +1804,18 @@ def get_contraction_indices(self) -> List[List[int]]:
                 current_position += 1
         return contraction_indices
 
-    def get_mapping_for_index(self, ind) -> List[_IndPos]:
+    def get_mapping_for_index(self, ind) -> list[_IndPos]:
         if ind >= self.number_of_contraction_indices:
             raise ValueError("index value exceeding the index range")
-        positions: List[_IndPos] = []
+        positions: list[_IndPos] = []
         for i, arg_with_ind in enumerate(self.args_with_ind):
             for j, arg_ind in enumerate(arg_with_ind.indices):
                 if ind == arg_ind:
                     positions.append(_IndPos(i, j))
         return positions
 
-    def get_contraction_indices_to_ind_rel_pos(self) -> List[List[_IndPos]]:
-        contraction_indices: List[List[_IndPos]] = [[] for i in range(self.number_of_contraction_indices)]
+    def get_contraction_indices_to_ind_rel_pos(self) -> list[list[_IndPos]]:
+        contraction_indices: list[list[_IndPos]] = [[] for i in range(self.number_of_contraction_indices)]
         for i, arg_with_ind in enumerate(self.args_with_ind):
             for j, ind in enumerate(arg_with_ind.indices):
                 if ind is not None:
@@ -1832,11 +1832,11 @@ def count_args_with_index(self, index: int) -> int:
                 counter += 1
         return counter
 
-    def get_args_with_index(self, index: int) -> List[_ArgE]:
+    def get_args_with_index(self, index: int) -> list[_ArgE]:
         """
         Get a list of arguments having the given index.
         """
-        ret: List[_ArgE] = [i for i in self.args_with_ind if index in i.indices]
+        ret: list[_ArgE] = [i for i in self.args_with_ind if index in i.indices]
         return ret
 
     @property
diff --git a/sympy/tensor/array/expressions/from_array_to_matrix.py b/sympy/tensor/array/expressions/from_array_to_matrix.py
index 8393519a003e..debfdd7eb5c4 100644
--- a/sympy/tensor/array/expressions/from_array_to_matrix.py
+++ b/sympy/tensor/array/expressions/from_array_to_matrix.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
 import itertools
 from collections import defaultdict
-from typing import Tuple as tTuple, Union as tUnion, FrozenSet, Dict as tDict, List, Optional
+from typing import FrozenSet
 from functools import singledispatch
 from itertools import accumulate
 
@@ -25,14 +26,14 @@
 from sympy.tensor.array.expressions.utils import _get_mapping_from_subranks
 
 
-def _get_candidate_for_matmul_from_contraction(scan_indices: List[Optional[int]], remaining_args: List[_ArgE]) -> tTuple[Optional[_ArgE], bool, int]:
+def _get_candidate_for_matmul_from_contraction(scan_indices: list[int | None], remaining_args: list[_ArgE]) -> tuple[_ArgE | None, bool, int]:
 
-    scan_indices_int: List[int] = [i for i in scan_indices if i is not None]
+    scan_indices_int: list[int] = [i for i in scan_indices if i is not None]
     if len(scan_indices_int) == 0:
         return None, False, -1
 
     transpose: bool = False
-    candidate: Optional[_ArgE] = None
+    candidate: _ArgE | None = None
     candidate_index: int = -1
     for arg_with_ind2 in remaining_args:
         if not isinstance(arg_with_ind2.element, MatrixExpr):
@@ -60,7 +61,7 @@ def _get_candidate_for_matmul_from_contraction(scan_indices: List[Optional[int]]
 
 def _insert_candidate_into_editor(editor: _EditArrayContraction, arg_with_ind: _ArgE, candidate: _ArgE, transpose1: bool, transpose2: bool):
     other = candidate.element
-    other_index: Optional[int]
+    other_index: int | None
     if transpose2:
         other = Transpose(other)
         other_index = candidate.indices[0]
@@ -140,12 +141,12 @@ def _find_trivial_matrices_rewrite(expr: ArrayTensorProduct):
     # "b*a*b.T"
 
     trivial_matrices = []
-    pos: Optional[int] = None
-    first: Optional[MatrixExpr] = None
-    second: Optional[MatrixExpr] = None
-    removed: List[int] = []
+    pos: int | None = None  # must be initialized else causes UnboundLocalError
+    first: MatrixExpr | None = None  # may cause UnboundLocalError if not initialized
+    second: MatrixExpr | None = None  # may cause UnboundLocalError if not initialized
+    removed: list[int] = []
     counter: int = 0
-    args: List[Optional[Basic]] = list(expr.args)
+    args: list[Basic | None] = list(expr.args)
     for i, arg in enumerate(expr.args):
         if isinstance(arg, MatrixExpr):
             if arg.shape == (1, 1):
@@ -168,7 +169,7 @@ def _find_trivial_matrices_rewrite(expr: ArrayTensorProduct):
 
 
 def _find_trivial_kronecker_products_broadcast(expr: ArrayTensorProduct):
-    newargs: List[Basic] = []
+    newargs: list[Basic] = []
     removed = []
     count_dims = 0
     for arg in expr.args:
@@ -217,7 +218,7 @@ def _(expr: ArrayContraction):
     if not isinstance(expr, ArrayContraction):
         return _array2matrix(expr)
     subexpr = expr.expr
-    contraction_indices: tTuple[tTuple[int]] = expr.contraction_indices
+    contraction_indices: tuple[tuple[int]] = expr.contraction_indices
     if contraction_indices == ((0,), (1,)) or (
         contraction_indices == ((0,),) and subexpr.shape[1] == 1
     ) or (
@@ -741,12 +742,12 @@ def _a2m_transpose(arg):
         return Transpose(arg).doit()
 
 
-def identify_hadamard_products(expr: tUnion[ArrayContraction, ArrayDiagonal]):
+def identify_hadamard_products(expr: ArrayContraction | ArrayDiagonal):
 
     editor: _EditArrayContraction = _EditArrayContraction(expr)
 
-    map_contr_to_args: tDict[FrozenSet, List[_ArgE]] = defaultdict(list)
-    map_ind_to_inds: tDict[Optional[int], int] = defaultdict(int)
+    map_contr_to_args: dict[FrozenSet, list[_ArgE]] = defaultdict(list)
+    map_ind_to_inds: dict[int | None, int] = defaultdict(int)
     for arg_with_ind in editor.args_with_ind:
         for ind in arg_with_ind.indices:
             map_ind_to_inds[ind] += 1
@@ -755,7 +756,7 @@ def identify_hadamard_products(expr: tUnion[ArrayContraction, ArrayDiagonal]):
         map_contr_to_args[frozenset(arg_with_ind.indices)].append(arg_with_ind)
 
     k: FrozenSet[int]
-    v: List[_ArgE]
+    v: list[_ArgE]
     for k, v in map_contr_to_args.items():
         make_trace: bool = False
         if len(k) == 1 and next(iter(k)) >= 0 and sum(next(iter(k)) in i for i in map_contr_to_args) == 1:
@@ -866,7 +867,7 @@ def identify_removable_identity_matrices(expr):
 
 def remove_identity_matrices(expr: ArrayContraction):
     editor = _EditArrayContraction(expr)
-    removed: List[int] = []
+    removed: list[int] = []
 
     permutation_map = {}
 
@@ -929,7 +930,7 @@ def remove_identity_matrices(expr: ArrayContraction):
     return ret_expr2, removed
 
 
-def _combine_removed(dim: int, removed1: List[int], removed2: List[int]) -> List[int]:
+def _combine_removed(dim: int, removed1: list[int], removed2: list[int]) -> list[int]:
     # Concatenate two axis removal operations as performed by
     # _remove_trivial_dims,
     removed1 = sorted(removed1)
@@ -955,7 +956,7 @@ def _combine_removed(dim: int, removed1: List[int], removed2: List[int]) -> List
 def _array_contraction_to_diagonal_multiple_identity(expr: ArrayContraction):
     editor = _EditArrayContraction(expr)
     editor.track_permutation_start()
-    removed: List[int] = []
+    removed: list[int] = []
     diag_index_counter: int = 0
     for i in range(editor.number_of_contraction_indices):
         identities = []
diff --git a/sympy/tensor/tensor.py b/sympy/tensor/tensor.py
index 3aafbce37d82..28f6094f7ca1 100644
--- a/sympy/tensor/tensor.py
+++ b/sympy/tensor/tensor.py
@@ -2880,7 +2880,7 @@ class Tensor(TensExpr):
 
     is_commutative = False
 
-    _index_structure = None  # type: _IndexStructure
+    _index_structure: _IndexStructure
     args: tuple[TensorHead, Tuple]
 
     def __new__(cls, tensor_head, indices, *, is_canon_bp=False, **kw_args):
@@ -3346,7 +3346,7 @@ def _eval_rewrite_as_Indexed(self, tens, indices, **kwargs):
         expr = Indexed(tens.args[0], *index_symbols)
         return self._check_add_Sum(expr, index_symbols)
 
-    def _eval_partial_derivative(self, s):  # type: (Tensor) -> Expr
+    def _eval_partial_derivative(self, s: Tensor) -> Expr:
 
         if not isinstance(s, Tensor):
             return S.Zero
@@ -3432,7 +3432,7 @@ class TensMul(TensExpr, AssocOp):
     """
     identity = S.One
 
-    _index_structure = None  # type: _IndexStructure
+    _index_structure: _IndexStructure
 
     def __new__(cls, *args, **kw_args):
         is_canon_bp = kw_args.get('is_canon_bp', False)


Code review comments:
diff_hunk: @@ -1193,7 +1194,7 @@ def evalf_integral(expr: 'Integral', prec: int, options: OPT_DICT) -> TMP_RES:
     return result
 
 
-def check_convergence(numer: 'Expr', denom: 'Expr', n: 'Symbol') -> tTuple[int, Any, Any]:
+def check_convergence(numer: Expr, denom: Expr, n: 'Symbol') -> tuple[int, Any, Any]:
path: sympy/core/evalf.py
text: @user1:
Can this be `Symbol`?

@author:
Looks like it can be! Went ahead and updated them plus Numbers

diff_hunk: @@ -147,7 +147,7 @@ def fastlog(x: Optional[MPF_TUP]) -> tUnion[int, Any]:
     return x[2] + x[3]
 
 
-def pure_complex(v: 'Expr', or_real=False) -> tuple['Number', 'Number'] | None:
+def pure_complex(v: Expr, or_real=False) -> tuple['Number', 'Number'] | None:
path: sympy/core/evalf.py
text: @user1:
I think that `'Number'` here can be `Number`.

diff_hunk: @@ -1235,7 +1236,7 @@ def check_convergence(numer: 'Expr', denom: 'Expr', n: 'Symbol') -> tTuple[int,
     return rate, constant, (qc - pc)/dpol.LC()
 
 
-def hypsum(expr: 'Expr', n: 'Symbol', start: int, prec: int) -> mpf:
+def hypsum(expr: Expr, n: 'Symbol', start: int, prec: int) -> mpf:
path: sympy/core/evalf.py
text: @user1:
Symbol?