From d1ab4554b813362505983a34bd561ae4dae8703b Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Wed, 10 Dec 2025 15:36:51 -0700 Subject: [PATCH 01/17] Add type aliases modules --- src/doc/en/reference/misc/index.rst | 6 ++---- src/sage/misc/types.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/sage/misc/types.py diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index bdc30927a55..b3f2e8c14ac 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -223,9 +223,7 @@ Distribution Credits ~~~~~~~ - -.. toctree:: - :maxdepth: 1 +.. toctree:: :maxdepth: 1 sage/misc/citation sage/misc/copying @@ -267,7 +265,7 @@ Miscellaneous Inspection and Development Tools sage/misc/classgraph sage/misc/dev_tools sage/misc/function_mangling - sage/misc/rest_index_of_methods + sage/misc/types Low-Level Utilities ------------------- diff --git a/src/sage/misc/types.py b/src/sage/misc/types.py new file mode 100644 index 00000000000..9ec23371246 --- /dev/null +++ b/src/sage/misc/types.py @@ -0,0 +1,28 @@ +r""" +Aliases for typing annotations + +This typing module defines some common aliases to be used for type annotations in Sage. +This module performs many possibly heavy imports, and so it should only be imported +within an ``if typing.TYPE_CHECKING`` block. This module will raise an error if it +is imported at runtime. +""" +# **************************************************************************** +# Copyright (C) 2025 Vincent Macri +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +import typing + +if typing.TYPE_CHECKING: + from sage.rings.integer import Integer + from sage.rings.finite_rings.integer_mod import IntegerMod_abstract + + type Int = Integer | int + type IntMod = Int | IntegerMod_abstract +else: + assert False, 'sage.misc.types cannot be imported at runtime, it may only be imported in an if `typing.TYPE_CHECKING` block From 040cea13160e8bcdd6c3e91e47ac0492a2ee559d Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Wed, 10 Dec 2025 15:58:46 -0700 Subject: [PATCH 02/17] Formatting --- src/sage/misc/types.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/sage/misc/types.py b/src/sage/misc/types.py index 9ec23371246..93d1e21fc2a 100644 --- a/src/sage/misc/types.py +++ b/src/sage/misc/types.py @@ -5,6 +5,10 @@ This module performs many possibly heavy imports, and so it should only be imported within an ``if typing.TYPE_CHECKING`` block. This module will raise an error if it is imported at runtime. + +The purpose of this module is to provide aliases when there are many types that +easily coerce into another type, or when the same code will give a different type +depending on whether the preparser is enabled or not. """ # **************************************************************************** # Copyright (C) 2025 Vincent Macri @@ -19,10 +23,10 @@ import typing if typing.TYPE_CHECKING: - from sage.rings.integer import Integer from sage.rings.finite_rings.integer_mod import IntegerMod_abstract + from sage.rings.integer import Integer type Int = Integer | int - type IntMod = Int | IntegerMod_abstract + type IntMod = IntegerMod_abstract | Int else: - assert False, 'sage.misc.types cannot be imported at runtime, it may only be imported in an if `typing.TYPE_CHECKING` block + assert False, 'sage.misc.types cannot be imported at runtime, it may only be imported in an if `typing.TYPE_CHECKING` block' From da6862fd013d46372406b7f52e15c80cb2900213 Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Wed, 10 Dec 2025 16:06:53 -0700 Subject: [PATCH 03/17] Use Int --- src/sage/misc/types.py | 5 +++++ src/sage/rings/factorint_pari.pyi | 13 +++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/sage/misc/types.py b/src/sage/misc/types.py index 93d1e21fc2a..4f6a4f43ae7 100644 --- a/src/sage/misc/types.py +++ b/src/sage/misc/types.py @@ -9,6 +9,11 @@ The purpose of this module is to provide aliases when there are many types that easily coerce into another type, or when the same code will give a different type depending on whether the preparser is enabled or not. + +Type unions defined in this module should usually only be used for annotating parameters, +for return values we should be able to give the exact type. If the return type depends +on the parameters, a union can be used as the return type and ``typing.overload`` +can be used to specify the return type for various combinations of parameters. """ # **************************************************************************** # Copyright (C) 2025 Vincent Macri diff --git a/src/sage/rings/factorint_pari.pyi b/src/sage/rings/factorint_pari.pyi index 4b0e9f6020d..d64943c9fcf 100644 --- a/src/sage/rings/factorint_pari.pyi +++ b/src/sage/rings/factorint_pari.pyi @@ -1,4 +1,13 @@ -from typing import Union +from typing import overload, Literal +from sage.misc.typing import Int -def factor_using_pari(n: Union[int, Integer], int_: bool = False, debug_level: int = 0, proof: bool | None = None) -> list[tuple[Union[int, Integer], int]]: +@overload +def factor_using_pari(n: Int, int_: Literal[True], debug_level: int = 0, proof: bool | None = None) -> list[tuple[int, int]]: + ... + +@overload +def factor_using_pari(n: Int, int_: Literal[False], debug_level: int = 0, proof: bool | None = None) -> list[tuple[Integer, int]]: + ... + +def factor_using_pari(n: Int, int_: bool = False, debug_level: int = 0, proof: bool | None = None) -> list[tuple[Int, int]]: ... From f409675d3767cf40a647e1423d33d494593b7a42 Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Wed, 10 Dec 2025 16:23:01 -0700 Subject: [PATCH 04/17] Add @overload for arithmetic between int and Integer --- src/sage/rings/integer.pyi | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/integer.pyi b/src/sage/rings/integer.pyi index 5d81a21b2b7..37febf421e8 100644 --- a/src/sage/rings/integer.pyi +++ b/src/sage/rings/integer.pyi @@ -1,3 +1,5 @@ +from typing import overload + from sage.libs.gmp.types import __mpz_struct, mpz_t, mpz_ptr from sage.libs.gmp.mpz import mpz_set @@ -22,37 +24,53 @@ class Integer(EuclideanDomainElement): def _add_(self, other: object) -> object: ... + @overload + def __add__(self, other: int) -> Integer: + ... + + @overload + def __radd__(self, other: int) -> Integer: + ... + def _mul_(self, other: object) -> object: ... + @overload + def __mul__(self, other: int) -> Integer: + ... + + @overload + def __rmul__(self, other: int) -> Integer: + ... + def _pow_(self, other: object) -> object: ... - def _and(self, other: 'Integer') -> 'Integer': + def _and(self, other: Integer) -> Integer: ... - def _or(self, other: 'Integer') -> 'Integer': + def _or(self, other: Integer) -> Integer: ... - def _xor(self, other: 'Integer') -> 'Integer': + def _xor(self, other: Integer) -> Integer: ... - def _exact_log_log2_iter(self, m: 'Integer') -> int: + def _exact_log_log2_iter(self, m: Integer) -> int: ... def _exact_log_mpfi_log(self, m: object) -> int: ... - def _valuation(self, p: 'Integer') -> RingElement: + def _valuation(self, p: Integer) -> RingElement: ... - def _val_unit(self, p: 'Integer') -> object: + def _val_unit(self, p: Integer) -> object: ... - def _divide_knowing_divisible_by(self, right: 'Integer') -> 'Integer': + def _divide_knowing_divisible_by(self, right: Integer) -> Integer: ... - def _is_power_of(self, n: 'Integer') -> bool: + def _is_power_of(self, n: Integer) -> bool: ... def _pseudoprime_is_prime(self, proof: object) -> bool: @@ -61,12 +79,12 @@ class Integer(EuclideanDomainElement): def mpz_set_str_python(z: mpz_ptr, s: str, base: int) -> int: ... -def smallInteger(value: int) -> 'Integer': +def smallInteger(value: int) -> Integer: ... _small_primes_table: list[bool] -def _Integer_from_mpz(e: mpz_t) -> 'Integer': +def _Integer_from_mpz(e: mpz_t) -> Integer: z = Integer.__new__(Integer) mpz_set(z.value, e) return z From 62f7d8c45ee375b840981667e023d6678ebc1ae2 Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Wed, 10 Dec 2025 17:32:00 -0700 Subject: [PATCH 05/17] Type annotations for Integer arithmetic --- src/mypy.ini | 2 +- src/sage/rings/integer.pyi | 64 ++++++++++++++++++++-------- src/sage/structure/element.pyi | 77 ++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 18 deletions(-) create mode 100644 src/sage/structure/element.pyi diff --git a/src/mypy.ini b/src/mypy.ini index 981c6e29c1b..7c01a3dab12 100644 --- a/src/mypy.ini +++ b/src/mypy.ini @@ -1,6 +1,6 @@ # https://mypy.readthedocs.io/en/stable/config_file.html [mypy] -python_version = 3.11 +python_version = 3.12 # https://github.com/sagemath/sage/issues/36198#issuecomment-1709521850 follow_imports = skip diff --git a/src/sage/rings/integer.pyi b/src/sage/rings/integer.pyi index 37febf421e8..ae1ccdccba5 100644 --- a/src/sage/rings/integer.pyi +++ b/src/sage/rings/integer.pyi @@ -1,10 +1,11 @@ -from typing import overload +from typing import overload, override from sage.libs.gmp.types import __mpz_struct, mpz_t, mpz_ptr from sage.libs.gmp.mpz import mpz_set from sage.structure.element import EuclideanDomainElement, RingElement from sage.categories.morphism import Morphism +from sage.misc.types import Int class Integer(EuclideanDomainElement): value: __mpz_struct[1] @@ -24,25 +25,9 @@ class Integer(EuclideanDomainElement): def _add_(self, other: object) -> object: ... - @overload - def __add__(self, other: int) -> Integer: - ... - - @overload - def __radd__(self, other: int) -> Integer: - ... - def _mul_(self, other: object) -> object: ... - @overload - def __mul__(self, other: int) -> Integer: - ... - - @overload - def __rmul__(self, other: int) -> Integer: - ... - def _pow_(self, other: object) -> object: ... @@ -76,6 +61,51 @@ class Integer(EuclideanDomainElement): def _pseudoprime_is_prime(self, proof: object) -> bool: ... + @overload + def __add__(self, other: int) -> Integer: ... + + @overload + def __add__(self, other: Integer) -> Integer: ... + + @overload + def __sub__(self, other: int) -> Integer: ... + + @overload + def __sub__(self, other: Integer) -> Integer: ... + + @overload + def __rsub__(self, other: int) -> Integer: ... + + @overload + def __rsub__(self, other: Integer) -> Integer: ... + + @overload + def __radd__(self, other: int) -> Integer: ... + + @overload + def __radd__(self, other: Integer) -> Integer: ... + + @overload + def __mul__(self, other: int) -> Integer: ... + + @overload + def __mul__(self, other: Integer) -> Integer: ... + + @overload + def __rmul__(self, other: int) -> Integer: ... + + @overload + def __rmul__(self, other: Integer) -> Integer: ... + + @overload + def __pow__(self, other: Int) -> Integer: ... + + @overload + def __pow__(self, other: int, modulo: Int | None) -> Integer: ... + + @overload + def __pow__(self, other: Integer, modulo: Int | None) -> Integer: ... + def mpz_set_str_python(z: mpz_ptr, s: str, base: int) -> int: ... diff --git a/src/sage/structure/element.pyi b/src/sage/structure/element.pyi new file mode 100644 index 00000000000..d61f5f0457d --- /dev/null +++ b/src/sage/structure/element.pyi @@ -0,0 +1,77 @@ +from sage.structure.sage_object import SageObject + +class Element(SageObject): + def __add__(self, other): ... + + def __sub__(self, other): ... + + def __neg__(self): ... + + def __mul__(self, other): ... + + def __matmul__(self, other): ... + + def __truediv__(self, other): ... + + def __floordiv__(self, other): ... + + def __mod__(self, other): ... + + def __pow__(self, other, modulo): ... + +class ElementWithCachedMethod(Element): + ... + +class ModuleElement(Element): + ... + +class ModuleElementWithMutability(ModuleElement): + ... + +class MonoidElement(Element): + ... + +class AdditiveGroupElement(ModuleElement): + ... + +class MultiplicativeGroupElement(MonoidElement): + ... + +class RingElement(ModuleElement): + ... + +class CommutativeRingElement(RingElement): + ... + +class Expression(CommutativeRingElement): + ... + +class Vector(ModuleElementWithMutability): + ... + +class Matrix(ModuleElement): + ... + +class IntegralDomainElement(CommutativeRingElement): + ... + +class DedekindDomainElement(IntegralDomainElement): + ... + +class PrincipalIdealDomainElement(DedekindDomainElement): + ... + +class EuclideanDomainElement(PrincipalIdealDomainElement): + ... + +class FieldElement(CommutativeRingElement): + ... + +class AlgebraElement(RingElement): + ... + +class CommutativeAlgebraElement(CommutativeRingElement): + ... + +class InfinityElement(RingElement): + ... From e35c25bf71e878f6b8d6803d6e16598510eba3d1 Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Thu, 11 Dec 2025 12:38:17 -0700 Subject: [PATCH 06/17] Import fix --- src/sage/misc/meson.build | 1 + src/sage/misc/types.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/misc/meson.build b/src/sage/misc/meson.build index f44095ff136..2a61fab7a83 100644 --- a/src/sage/misc/meson.build +++ b/src/sage/misc/meson.build @@ -110,6 +110,7 @@ py.install_sources( 'test_nested_class.py', 'timing.py', 'trace.py', + 'types.py', 'unknown.py', 'verbose.py', 'viewer.py', diff --git a/src/sage/misc/types.py b/src/sage/misc/types.py index 4f6a4f43ae7..b257dd46ac2 100644 --- a/src/sage/misc/types.py +++ b/src/sage/misc/types.py @@ -15,6 +15,7 @@ on the parameters, a union can be used as the return type and ``typing.overload`` can be used to specify the return type for various combinations of parameters. """ + # **************************************************************************** # Copyright (C) 2025 Vincent Macri # @@ -33,5 +34,3 @@ type Int = Integer | int type IntMod = IntegerMod_abstract | Int -else: - assert False, 'sage.misc.types cannot be imported at runtime, it may only be imported in an if `typing.TYPE_CHECKING` block' From 0c5b2811e3253b8c225faf595fad32ce06d1e3af Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Tue, 13 Jan 2026 17:21:28 -0700 Subject: [PATCH 07/17] Remove types.py --- src/doc/en/reference/misc/index.rst | 1 - src/sage/misc/types.py | 36 ----------------------------- src/sage/rings/factorint_pari.pyi | 2 +- src/sage/rings/integer.pyi | 3 ++- src/sage/rings/integer.pyx | 3 +++ src/sage/test_typing.py | 17 ++++++++++++++ 6 files changed, 23 insertions(+), 39 deletions(-) delete mode 100644 src/sage/misc/types.py create mode 100644 src/sage/test_typing.py diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index b3f2e8c14ac..b5f26f6493d 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -265,7 +265,6 @@ Miscellaneous Inspection and Development Tools sage/misc/classgraph sage/misc/dev_tools sage/misc/function_mangling - sage/misc/types Low-Level Utilities ------------------- diff --git a/src/sage/misc/types.py b/src/sage/misc/types.py deleted file mode 100644 index b257dd46ac2..00000000000 --- a/src/sage/misc/types.py +++ /dev/null @@ -1,36 +0,0 @@ -r""" -Aliases for typing annotations - -This typing module defines some common aliases to be used for type annotations in Sage. -This module performs many possibly heavy imports, and so it should only be imported -within an ``if typing.TYPE_CHECKING`` block. This module will raise an error if it -is imported at runtime. - -The purpose of this module is to provide aliases when there are many types that -easily coerce into another type, or when the same code will give a different type -depending on whether the preparser is enabled or not. - -Type unions defined in this module should usually only be used for annotating parameters, -for return values we should be able to give the exact type. If the return type depends -on the parameters, a union can be used as the return type and ``typing.overload`` -can be used to specify the return type for various combinations of parameters. -""" - -# **************************************************************************** -# Copyright (C) 2025 Vincent Macri -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# https://www.gnu.org/licenses/ -# **************************************************************************** - -import typing - -if typing.TYPE_CHECKING: - from sage.rings.finite_rings.integer_mod import IntegerMod_abstract - from sage.rings.integer import Integer - - type Int = Integer | int - type IntMod = IntegerMod_abstract | Int diff --git a/src/sage/rings/factorint_pari.pyi b/src/sage/rings/factorint_pari.pyi index d64943c9fcf..101dd4cf745 100644 --- a/src/sage/rings/factorint_pari.pyi +++ b/src/sage/rings/factorint_pari.pyi @@ -1,5 +1,5 @@ from typing import overload, Literal -from sage.misc.typing import Int +from sage.rings.integer import Int @overload def factor_using_pari(n: Int, int_: Literal[True], debug_level: int = 0, proof: bool | None = None) -> list[tuple[int, int]]: diff --git a/src/sage/rings/integer.pyi b/src/sage/rings/integer.pyi index ae1ccdccba5..ede5f2f3d26 100644 --- a/src/sage/rings/integer.pyi +++ b/src/sage/rings/integer.pyi @@ -5,7 +5,8 @@ from sage.libs.gmp.mpz import mpz_set from sage.structure.element import EuclideanDomainElement, RingElement from sage.categories.morphism import Morphism -from sage.misc.types import Int + +Int = Integer | int class Integer(EuclideanDomainElement): value: __mpz_struct[1] diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index 358aac0339c..4724c67dd9c 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -8002,6 +8002,9 @@ cdef double mpz_get_d_nearest(mpz_t x) except? -648555075988944.5: import numbers numbers.Integral.register(Integer) +# Define a type alias that can be used in type annotations and at runtime +Int = Integer | int + # Free the memory used by the integer pool when sage exits. This is # not strictly necessary because the OS should immediately reclaim # these resources when sage terminates. However, it may aid valgrind diff --git a/src/sage/test_typing.py b/src/sage/test_typing.py new file mode 100644 index 00000000000..3b47dca0bbc --- /dev/null +++ b/src/sage/test_typing.py @@ -0,0 +1,17 @@ +from sage.rings.integer import Integer +from typing import reveal_type + +a = Integer(1) +b = 2 + +reveal_type(a + b) +reveal_type(b + a) + +reveal_type(a - b) +reveal_type(b - a) + +reveal_type(a * b) +reveal_type(b * a) + +reveal_type(a ** b) +reveal_type(b ** a) From 73a165bb3e0c069356d1e1c9de8901129336a658 Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Tue, 13 Jan 2026 17:24:30 -0700 Subject: [PATCH 08/17] Undo changes to misc/index.rst --- src/doc/en/reference/misc/index.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index b5f26f6493d..bdc30927a55 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -223,7 +223,9 @@ Distribution Credits ~~~~~~~ -.. toctree:: :maxdepth: 1 + +.. toctree:: + :maxdepth: 1 sage/misc/citation sage/misc/copying @@ -265,6 +267,7 @@ Miscellaneous Inspection and Development Tools sage/misc/classgraph sage/misc/dev_tools sage/misc/function_mangling + sage/misc/rest_index_of_methods Low-Level Utilities ------------------- From c597bf478b7d4a3b54682e3cd5894251906259d9 Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Wed, 14 Jan 2026 11:31:21 -0700 Subject: [PATCH 09/17] Basic version of integer annotations --- src/sage/categories/morphism.pyi | 3 ++ src/sage/rings/integer.pyi | 45 ++++++++++-------------------- src/sage/rings/rational.pyi | 4 +++ src/sage/structure/element.pyi | 1 + src/sage/structure/sage_object.pyi | 2 ++ src/sage/test_typing.py | 17 ----------- src/test_typing.py | 17 +++++++++++ 7 files changed, 42 insertions(+), 47 deletions(-) create mode 100644 src/sage/categories/morphism.pyi create mode 100644 src/sage/rings/rational.pyi create mode 100644 src/sage/structure/sage_object.pyi delete mode 100644 src/sage/test_typing.py create mode 100644 src/test_typing.py diff --git a/src/sage/categories/morphism.pyi b/src/sage/categories/morphism.pyi new file mode 100644 index 00000000000..108497e6243 --- /dev/null +++ b/src/sage/categories/morphism.pyi @@ -0,0 +1,3 @@ + +class Morphism: + ... diff --git a/src/sage/rings/integer.pyi b/src/sage/rings/integer.pyi index ede5f2f3d26..9a61c407e1a 100644 --- a/src/sage/rings/integer.pyi +++ b/src/sage/rings/integer.pyi @@ -1,18 +1,12 @@ -from typing import overload, override +from __future__ import annotations -from sage.libs.gmp.types import __mpz_struct, mpz_t, mpz_ptr -from sage.libs.gmp.mpz import mpz_set +from typing import Any, overload from sage.structure.element import EuclideanDomainElement, RingElement -from sage.categories.morphism import Morphism - -Int = Integer | int class Integer(EuclideanDomainElement): - value: __mpz_struct[1] - def set_from_mpz(self, value: mpz_t) -> None: - ... + def __init__(self, x=None, base=0): ... def hash_c(self) -> int: ... @@ -63,59 +57,50 @@ class Integer(EuclideanDomainElement): ... @overload - def __add__(self, other: int) -> Integer: ... + def __add__(self, other: Int) -> Integer: ... @overload - def __add__(self, other: Integer) -> Integer: ... + def __add__(self, other: Any) -> Any: ... @overload - def __sub__(self, other: int) -> Integer: ... + def __sub__(self, other: Int) -> Integer: ... @overload - def __sub__(self, other: Integer) -> Integer: ... + def __sub__(self, other: Any) -> Any: ... @overload def __rsub__(self, other: int) -> Integer: ... @overload - def __rsub__(self, other: Integer) -> Integer: ... + def __rsub__(self, other: Any) -> Any: ... @overload def __radd__(self, other: int) -> Integer: ... @overload - def __radd__(self, other: Integer) -> Integer: ... + def __radd__(self, other: Any) -> Any: ... @overload - def __mul__(self, other: int) -> Integer: ... + def __mul__(self, other: Int) -> Integer: ... @overload - def __mul__(self, other: Integer) -> Integer: ... + def __mul__(self, other: Any) -> Any: ... @overload def __rmul__(self, other: int) -> Integer: ... @overload - def __rmul__(self, other: Integer) -> Integer: ... + def __rmul__(self, other: Any) -> Any: ... @overload def __pow__(self, other: Int) -> Integer: ... @overload - def __pow__(self, other: int, modulo: Int | None) -> Integer: ... + def __pow__(self, other: Int, modulo: Int | None) -> Integer: ... @overload - def __pow__(self, other: Integer, modulo: Int | None) -> Integer: ... + def __pow__(self, other: Any, modulo: Any) -> Any: ... -def mpz_set_str_python(z: mpz_ptr, s: str, base: int) -> int: - ... - -def smallInteger(value: int) -> Integer: - ... +Int = Integer | int _small_primes_table: list[bool] - -def _Integer_from_mpz(e: mpz_t) -> Integer: - z = Integer.__new__(Integer) - mpz_set(z.value, e) - return z diff --git a/src/sage/rings/rational.pyi b/src/sage/rings/rational.pyi new file mode 100644 index 00000000000..5c925c1488d --- /dev/null +++ b/src/sage/rings/rational.pyi @@ -0,0 +1,4 @@ +from sage.structure.element import FieldElement + +class Rational(FieldElement): + ... diff --git a/src/sage/structure/element.pyi b/src/sage/structure/element.pyi index d61f5f0457d..e61458d665c 100644 --- a/src/sage/structure/element.pyi +++ b/src/sage/structure/element.pyi @@ -1,6 +1,7 @@ from sage.structure.sage_object import SageObject class Element(SageObject): + def __add__(self, other): ... def __sub__(self, other): ... diff --git a/src/sage/structure/sage_object.pyi b/src/sage/structure/sage_object.pyi new file mode 100644 index 00000000000..128f15fd645 --- /dev/null +++ b/src/sage/structure/sage_object.pyi @@ -0,0 +1,2 @@ +class SageObject: + ... diff --git a/src/sage/test_typing.py b/src/sage/test_typing.py deleted file mode 100644 index 3b47dca0bbc..00000000000 --- a/src/sage/test_typing.py +++ /dev/null @@ -1,17 +0,0 @@ -from sage.rings.integer import Integer -from typing import reveal_type - -a = Integer(1) -b = 2 - -reveal_type(a + b) -reveal_type(b + a) - -reveal_type(a - b) -reveal_type(b - a) - -reveal_type(a * b) -reveal_type(b * a) - -reveal_type(a ** b) -reveal_type(b ** a) diff --git a/src/test_typing.py b/src/test_typing.py new file mode 100644 index 00000000000..9cf9d68baae --- /dev/null +++ b/src/test_typing.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from typing import overload, reveal_type + +from sage.rings.integer import Integer, Int + +reveal_type(Integer) +reveal_type(Int) + +a: Integer = Integer(1) +b: int = 2 + +c = a + b +d = b + a + +reveal_type(c) +reveal_type(d) From 034bbf989d53e63dd438af3d3f2d75ecb2396a84 Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Mon, 19 Jan 2026 12:59:14 -0700 Subject: [PATCH 10/17] Meson update --- src/sage/misc/meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/misc/meson.build b/src/sage/misc/meson.build index 6d66225fc8c..e56df568eb2 100644 --- a/src/sage/misc/meson.build +++ b/src/sage/misc/meson.build @@ -110,7 +110,6 @@ py.install_sources( 'test_nested_class.py', 'timing.py', 'trace.py', - 'types.py', 'unknown.py', 'verbose.py', 'viewer.py', From b5599831f5c7af23c6f45a4223672e053e816808 Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Mon, 19 Jan 2026 13:13:56 -0700 Subject: [PATCH 11/17] Remove test file --- src/test_typing.py | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 src/test_typing.py diff --git a/src/test_typing.py b/src/test_typing.py deleted file mode 100644 index 9cf9d68baae..00000000000 --- a/src/test_typing.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import annotations - -from typing import overload, reveal_type - -from sage.rings.integer import Integer, Int - -reveal_type(Integer) -reveal_type(Int) - -a: Integer = Integer(1) -b: int = 2 - -c = a + b -d = b + a - -reveal_type(c) -reveal_type(d) From a6ed15444261a394c0388b204bbc56fdd04d93fb Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Mon, 19 Jan 2026 13:34:05 -0700 Subject: [PATCH 12/17] Integer typing --- src/sage/rings/integer.pyi | 50 +++++++++++--------------------------- src/sage/rings/integer.pyx | 4 ++- 2 files changed, 17 insertions(+), 37 deletions(-) diff --git a/src/sage/rings/integer.pyi b/src/sage/rings/integer.pyi index 9a61c407e1a..9eadfc11e92 100644 --- a/src/sage/rings/integer.pyi +++ b/src/sage/rings/integer.pyi @@ -8,53 +8,33 @@ class Integer(EuclideanDomainElement): def __init__(self, x=None, base=0): ... - def hash_c(self) -> int: - ... + def __pari__(self) -> object: ... - def __pari__(self) -> object: - ... + def _shift_helper(self, y: object, sign: int) -> object: ... - def _shift_helper(self, y: object, sign: int) -> object: - ... + def _add_(self, other: object) -> object: ... - def _add_(self, other: object) -> object: - ... + def _mul_(self, other: object) -> object: ... - def _mul_(self, other: object) -> object: - ... + def _pow_(self, other: object) -> object: ... - def _pow_(self, other: object) -> object: - ... + def _exact_log_log2_iter(self, m: Integer) -> int: ... - def _and(self, other: Integer) -> Integer: - ... + def _exact_log_mpfi_log(self, m: object) -> int: ... - def _or(self, other: Integer) -> Integer: - ... + def _valuation(self, p: Integer) -> RingElement: ... - def _xor(self, other: Integer) -> Integer: - ... + def valuation(self, p: Int) -> RingElement: ... - def _exact_log_log2_iter(self, m: Integer) -> int: - ... + ord = valuation - def _exact_log_mpfi_log(self, m: object) -> int: - ... + def val_unit(self, p: Int) -> object: ... - def _valuation(self, p: Integer) -> RingElement: - ... + def divide_knowing_divisible_by(self, right: Int) -> Integer: ... - def _val_unit(self, p: Integer) -> object: - ... + def is_power_of(self, n: Integer) -> bool: ... - def _divide_knowing_divisible_by(self, right: Integer) -> Integer: - ... - - def _is_power_of(self, n: Integer) -> bool: - ... - - def _pseudoprime_is_prime(self, proof: object) -> bool: - ... + def is_pseudoprime(self) -> bool: ... @overload def __add__(self, other: Int) -> Integer: ... @@ -102,5 +82,3 @@ class Integer(EuclideanDomainElement): def __pow__(self, other: Any, modulo: Any) -> Any: ... Int = Integer | int - -_small_primes_table: list[bool] diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index 26772cdc7ec..0e5e3883db5 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -4580,6 +4580,8 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): sage: a.divide_knowing_divisible_by(11) # way off and possibly random 43215361478743422388970455040 """ + if not isinstance(right, Integer): + right = Integer(right) return self._divide_knowing_divisible_by(right) def _lcm(self, Integer n): @@ -5206,7 +5208,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): else: return 0 - def is_power_of(Integer self, n): + def is_power_of(self, n) -> bool: r""" Return ``True`` if there is an integer `b` with `\mathtt{self} = n^b`. From 340b8c00adedacbdce3805d40d1d3d287d4c7597 Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Mon, 19 Jan 2026 13:53:40 -0700 Subject: [PATCH 13/17] Integer stub ruff fixes --- src/sage/rings/integer.pyi | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/sage/rings/integer.pyi b/src/sage/rings/integer.pyi index 9eadfc11e92..d091c7c3977 100644 --- a/src/sage/rings/integer.pyi +++ b/src/sage/rings/integer.pyi @@ -1,12 +1,10 @@ -from __future__ import annotations - -from typing import Any, overload +from typing import overload from sage.structure.element import EuclideanDomainElement, RingElement class Integer(EuclideanDomainElement): - def __init__(self, x=None, base=0): ... + def __init__(self, x: object = None, base: Int = 0) -> None: ... def __pari__(self) -> object: ... @@ -40,37 +38,37 @@ class Integer(EuclideanDomainElement): def __add__(self, other: Int) -> Integer: ... @overload - def __add__(self, other: Any) -> Any: ... + def __add__(self, other: object) -> object: ... @overload def __sub__(self, other: Int) -> Integer: ... @overload - def __sub__(self, other: Any) -> Any: ... + def __sub__(self, other: object) -> object: ... @overload def __rsub__(self, other: int) -> Integer: ... @overload - def __rsub__(self, other: Any) -> Any: ... + def __rsub__(self, other: object) -> object: ... @overload def __radd__(self, other: int) -> Integer: ... @overload - def __radd__(self, other: Any) -> Any: ... + def __radd__(self, other: object) -> object: ... @overload def __mul__(self, other: Int) -> Integer: ... @overload - def __mul__(self, other: Any) -> Any: ... + def __mul__(self, other: object) -> object: ... @overload def __rmul__(self, other: int) -> Integer: ... @overload - def __rmul__(self, other: Any) -> Any: ... + def __rmul__(self, other: object) -> object: ... @overload def __pow__(self, other: Int) -> Integer: ... @@ -79,6 +77,6 @@ class Integer(EuclideanDomainElement): def __pow__(self, other: Int, modulo: Int | None) -> Integer: ... @overload - def __pow__(self, other: Any, modulo: Any) -> Any: ... + def __pow__(self, other: object, modulo: object | None) -> object: ... -Int = Integer | int +Int = Integer | int # noqa: PYI026 this is not a TypeAlias because it should be usable in isinstance checks From 8635688d8afb62b29ff7f660f8948445938dc1b2 Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Mon, 19 Jan 2026 13:54:40 -0700 Subject: [PATCH 14/17] ruff format --- src/sage/rings/integer.pyi | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/sage/rings/integer.pyi b/src/sage/rings/integer.pyi index d091c7c3977..1504be86833 100644 --- a/src/sage/rings/integer.pyi +++ b/src/sage/rings/integer.pyi @@ -3,79 +3,51 @@ from typing import overload from sage.structure.element import EuclideanDomainElement, RingElement class Integer(EuclideanDomainElement): - def __init__(self, x: object = None, base: Int = 0) -> None: ... - def __pari__(self) -> object: ... - def _shift_helper(self, y: object, sign: int) -> object: ... - def _add_(self, other: object) -> object: ... - def _mul_(self, other: object) -> object: ... - def _pow_(self, other: object) -> object: ... - def _exact_log_log2_iter(self, m: Integer) -> int: ... - def _exact_log_mpfi_log(self, m: object) -> int: ... - def _valuation(self, p: Integer) -> RingElement: ... - def valuation(self, p: Int) -> RingElement: ... ord = valuation def val_unit(self, p: Int) -> object: ... - def divide_knowing_divisible_by(self, right: Int) -> Integer: ... - def is_power_of(self, n: Integer) -> bool: ... - def is_pseudoprime(self) -> bool: ... - @overload def __add__(self, other: Int) -> Integer: ... - @overload def __add__(self, other: object) -> object: ... - @overload def __sub__(self, other: Int) -> Integer: ... - @overload def __sub__(self, other: object) -> object: ... - @overload def __rsub__(self, other: int) -> Integer: ... - @overload def __rsub__(self, other: object) -> object: ... - @overload def __radd__(self, other: int) -> Integer: ... - @overload def __radd__(self, other: object) -> object: ... - @overload def __mul__(self, other: Int) -> Integer: ... - @overload def __mul__(self, other: object) -> object: ... - @overload def __rmul__(self, other: int) -> Integer: ... - @overload def __rmul__(self, other: object) -> object: ... - @overload def __pow__(self, other: Int) -> Integer: ... - @overload def __pow__(self, other: Int, modulo: Int | None) -> Integer: ... - @overload def __pow__(self, other: object, modulo: object | None) -> object: ... From 5de72ab12b3f7d3530c496d864abc222ab11b40f Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Mon, 19 Jan 2026 15:51:52 -0700 Subject: [PATCH 15/17] Remove Int at runtime --- src/sage/rings/integer.pyi | 3 ++- src/sage/rings/integer.pyx | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/integer.pyi b/src/sage/rings/integer.pyi index 1504be86833..34fbc7784af 100644 --- a/src/sage/rings/integer.pyi +++ b/src/sage/rings/integer.pyi @@ -51,4 +51,5 @@ class Integer(EuclideanDomainElement): @overload def __pow__(self, other: object, modulo: object | None) -> object: ... -Int = Integer | int # noqa: PYI026 this is not a TypeAlias because it should be usable in isinstance checks +# This type alias is not available at runtime +type Int = Integer | int diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index 0e5e3883db5..5536963dc0a 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -7998,9 +7998,6 @@ cdef double mpz_get_d_nearest(mpz_t x) except? -648555075988944.5: import numbers numbers.Integral.register(Integer) -# Define a type alias that can be used in type annotations and at runtime -Int = Integer | int - # Free the memory used by the integer pool when sage exits. This is # not strictly necessary because the OS should immediately reclaim # these resources when sage terminates. However, it may aid valgrind From d8c0ac2f0e1ac2c1a6cd8d03d9eb4ac04bdfd2ae Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Tue, 27 Jan 2026 16:10:49 -0700 Subject: [PATCH 16/17] Add test, cleanup --- src/sage/rings/integer.pyx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index dc8bbd45ad6..5e6d61a6093 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -4578,10 +4578,15 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): 107326510974310118493 sage: a.divide_knowing_divisible_by(11) # way off and possibly random 43215361478743422388970455040 + + TESTS: + + Make sure the methods works for Python ints:: + + sage: 10.divide_knowing_divisible_by(5r) + 2 """ - if not isinstance(right, Integer): - right = Integer(right) - return self._divide_knowing_divisible_by(right) + return self._divide_knowing_divisible_by(as_Integer(right)) def _lcm(self, Integer n): """ From 16dc5af1fb2bb6dbae65663c69d51dffcf6eb478 Mon Sep 17 00:00:00 2001 From: Vincent Macri Date: Tue, 27 Jan 2026 17:09:23 -0700 Subject: [PATCH 17/17] More annotations for Integer --- src/mypy.ini | 2 +- src/sage/rings/integer.pyi | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mypy.ini b/src/mypy.ini index 7c01a3dab12..fceb5f3537d 100644 --- a/src/mypy.ini +++ b/src/mypy.ini @@ -3,5 +3,5 @@ python_version = 3.12 # https://github.com/sagemath/sage/issues/36198#issuecomment-1709521850 -follow_imports = skip +follow_imports = silent ignore_missing_imports = True diff --git a/src/sage/rings/integer.pyi b/src/sage/rings/integer.pyi index 34fbc7784af..89f297aae19 100644 --- a/src/sage/rings/integer.pyi +++ b/src/sage/rings/integer.pyi @@ -6,9 +6,10 @@ class Integer(EuclideanDomainElement): def __init__(self, x: object = None, base: Int = 0) -> None: ... def __pari__(self) -> object: ... def _shift_helper(self, y: object, sign: int) -> object: ... - def _add_(self, other: object) -> object: ... - def _mul_(self, other: object) -> object: ... - def _pow_(self, other: object) -> object: ... + def _add_(self, other: Integer) -> Integer: ... + def _sub_(self, other: Integer) -> Integer: ... + def _mul_(self, other: Integer) -> Integer: ... + def _pow_(self, other: Integer) -> Integer: ... def _exact_log_log2_iter(self, m: Integer) -> int: ... def _exact_log_mpfi_log(self, m: object) -> int: ... def _valuation(self, p: Integer) -> RingElement: ...