Skip to content

Commit 732d33f

Browse files
authored
Add CI job for mypy (#1150)
I've fixed up the remaining mypy errors outside of: - `mathics.builtin` and `mathics.eval`: these still have quite a few errors, but are going to be a bigger job to go through than the core modules, and are probably easier to just knock off one by one incrementally - `test/` which I'd say are lower priority for type checking - `mathics/benchmark.py`: is this still used? It just crashes when I try to run it For now I've just set the mypy config to ignore these directories so we can run a CI job to ensure that the other core modules continue to typecheck, but might be better to be a bit more selective. There were also a couple bits of code I've commented out as they appeared to be dead as best I could tell, but tell me if I'm mistaken.
1 parent a39a3e9 commit 732d33f

File tree

25 files changed

+244
-155
lines changed

25 files changed

+244
-155
lines changed

.github/workflows/mypy.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Mathics3 (Type checking)
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ['3.12']
15+
steps:
16+
- uses: actions/checkout@v4
17+
- name: Set up Python ${{ matrix.python-version }}
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: ${{ matrix.python-version }}
21+
- name: Install dependencies
22+
run: |
23+
sudo apt update -qq && sudo apt install llvm-dev remake
24+
python -m pip install --upgrade pip
25+
# We can comment out after next Mathics-Scanner release
26+
# python -m pip install -e git+https://github.com/Mathics3/mathics-scanner#egg=Mathics-Scanner[full]
27+
git clone https://github.com/Mathics3/mathics-scanner.git
28+
cd mathics-scanner/
29+
pip install -e .
30+
cd ..
31+
32+
- name: Install Mathics with minimum dependencies
33+
run: |
34+
make develop
35+
- name: Run mypy
36+
run: |
37+
pip install mypy==1.13 sympy==1.12
38+
touch ./mathics-scanner/mathics_scanner/py.typed
39+
pip install ./mathics-scanner/
40+
mypy --install-types --non-interactive mathics

mathics/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import platform
44
import sys
55
from importlib import import_module
6-
from typing import Dict
6+
from typing import Dict, Tuple
77

88
from mpmath import __version__ as mpmath_version
99
from numpy import __version__ as numpy_version
@@ -26,7 +26,7 @@
2626

2727
# optional_software contains a list of Python packages
2828
# that add functionality but are optional
29-
optional_software: Dict[str, str] = (
29+
optional_software: Tuple[str, ...] = (
3030
"cython",
3131
"lxml",
3232
"matplotlib",

mathics/builtin/box/graphics.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88

99
from math import atan2, ceil, cos, degrees, floor, log10, pi, sin
10+
from typing import Optional
1011

1112
from mathics.builtin.box.expression import BoxExpression
1213
from mathics.builtin.colors.color_directives import (
@@ -48,7 +49,7 @@
4849

4950
# Note: has to come before _ArcBox
5051
class _RoundBox(_GraphicsElementBox):
51-
face_element = None
52+
face_element: Optional[bool] = None
5253

5354
def init(self, graphics, style, item):
5455
super(_RoundBox, self).init(graphics, item, style)

mathics/builtin/quantum_mechanics/angular.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,10 @@ class PauliMatrix(SympyFunction):
106106
>> PauliMatrix[1] . PauliMatrix[2] == I PauliMatrix[3]
107107
= True
108108
109-
>> MatrixExp[I \[Phi]/2 PauliMatrix[3]]
109+
>> MatrixExp[I \\[Phi]/2 PauliMatrix[3]]
110110
= {{E ^ (I / 2 ϕ), 0}, {0, E ^ ((-I / 2) ϕ)}}
111111
112-
>> % /. \[Phi] -> 2 Pi
112+
>> % /. \\[Phi] -> 2 Pi
113113
= {{-1, 0}, {0, -1}}
114114
"""
115115

mathics/core/atoms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77
from typing import Any, Dict, Generic, Optional, Tuple, TypeVar, Union
88

9-
import mpmath # type: ignore[import-untyped]
9+
import mpmath
1010
import sympy
1111

1212
from mathics.core.element import BoxElementMixin, ImmutableValueMixin

mathics/core/builtin.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
cast,
2727
)
2828

29-
import mpmath # type: ignore[import-untyped]
29+
import mpmath
3030
import pkg_resources
3131
import sympy
3232

@@ -517,6 +517,8 @@ def is_literal(self) -> bool:
517517

518518

519519
class BuiltinElement(Builtin, BaseElement):
520+
options: Dict[str, Any]
521+
520522
def __new__(cls, *args, **kwargs):
521523
new_kwargs = kwargs.copy()
522524
# In a Builtin element, we never return an Expression object,
@@ -1212,6 +1214,7 @@ class PatternObject(BuiltinElement, BasePattern):
12121214
needs_verbatim = True
12131215

12141216
arg_counts: List[int] = []
1217+
options: Dict[str, Any]
12151218

12161219
def init(self, expr: Expression, evaluation: Optional[Evaluation] = None):
12171220
super().init(expr, evaluation=evaluation)

mathics/core/convert/mpmath.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from functools import lru_cache
44
from typing import Optional, Union
55

6-
import mpmath # type: ignore[import-untyped]
6+
import mpmath
77
import sympy
88

99
from mathics.core.atoms import Complex, MachineReal, MachineReal0, PrecisionReal

mathics/core/element.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,9 @@ class BaseElement(KeyComparable, ABC):
208208
Some important subclasses: Atom and Expression.
209209
"""
210210

211-
options: dict
211+
options: Optional[Dict[str, Any]]
212212
last_evaluated: Any
213+
unevaluated: bool
213214
# this variable holds a function defined in mathics.core.expression that creates an expression
214215
create_expression: Any
215216

mathics/core/evaluation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def __init__(
172172
self.out: List[_Out] = []
173173
self.output = output if output else Output()
174174
self.listeners: Dict[str, List[Callable]] = {}
175-
self.options: Optional[tuple] = None
175+
self.options: Optional[Dict[str, Any]] = None
176176
self.predetermined_out = None
177177

178178
self.quiet_all = False

mathics/core/expression.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
List,
1414
Optional,
1515
Sequence,
16+
Set,
1617
Tuple,
17-
Type,
1818
Union,
1919
)
2020

@@ -273,7 +273,7 @@ class Expression(BaseElement, NumericOperators, EvalMixin):
273273
_sequences: Any
274274
_cache: Optional[ExpressionCache]
275275
elements_properties: Optional[ElementsProperties]
276-
options: Optional[tuple]
276+
options: Optional[Dict[str, Any]]
277277
pattern_sequence: bool
278278

279279
def __init__(
@@ -535,11 +535,11 @@ def evaluate(
535535
if evaluation.timeout:
536536
return None
537537

538-
expr = self
538+
expr: Optional[BaseElement] = self
539539
reevaluate = True
540540
limit = None
541541
iteration = 1
542-
names = set()
542+
names: Set[str] = set()
543543
definitions = evaluation.definitions
544544

545545
old_options = evaluation.options
@@ -553,6 +553,8 @@ def evaluate(
553553
try:
554554
# Evaluation loop:
555555
while reevaluate:
556+
assert isinstance(expr, EvalMixin)
557+
556558
# If definitions have not changed in the last evaluation,
557559
# then evaluating again will produce the same result
558560
if not expr.is_uncertain_final_definitions(definitions):
@@ -702,16 +704,20 @@ def flatten_with_respect_to_head(
702704
sub_level = level - 1
703705
do_flatten = False
704706
for element in self._elements:
705-
if element.get_head().sameQ(head) and (
706-
not pattern_only or element.pattern_sequence
707+
if (
708+
isinstance(element, Expression)
709+
and element.get_head().sameQ(head)
710+
and (not pattern_only or element.pattern_sequence)
707711
):
708712
do_flatten = True
709713
break
710714
if do_flatten:
711-
new_elements = []
715+
new_elements: List[BaseElement] = []
712716
for element in self._elements:
713-
if element.get_head().sameQ(head) and (
714-
not pattern_only or element.pattern_sequence
717+
if (
718+
isinstance(element, Expression)
719+
and element.get_head().sameQ(head)
720+
and (not pattern_only or element.pattern_sequence)
715721
):
716722
new_element = element.flatten_with_respect_to_head(
717723
head, pattern_only, callback, level=sub_level
@@ -953,14 +959,16 @@ def get_sort_key(self, pattern_sort=False) -> tuple:
953959
3: tuple: list of Elements
954960
4: 1: No clue...
955961
"""
956-
exps: Dict[str, float] = {}
962+
exps: Dict[str, Union[float, complex]] = {}
957963
head = self._head
958964
if head is SymbolTimes:
959965
for element in self.elements:
960966
name = element.get_name()
961967
if element.has_form("Power", 2):
962968
var = element.get_element(0).get_name()
963-
exp = element.get_element(1).round_to_float()
969+
expr = element.get_element(1)
970+
assert isinstance(expr, (Expression, NumericOperators))
971+
exp = expr.round_to_float()
964972
if var and exp is not None:
965973
exps[var] = exps.get(var, 0) + exp
966974
elif name:
@@ -1042,6 +1050,7 @@ def is_uncertain_final_definitions(self, definitions) -> bool:
10421050

10431051
if cache.symbols is None:
10441052
cache = self._rebuild_cache()
1053+
assert cache is not None
10451054

10461055
return definitions.is_uncertain_final_value(time, cache.symbols)
10471056

@@ -1123,7 +1132,7 @@ def restructure(self, head, elements, evaluation, structure_cache=None, deps=Non
11231132
s = structure(head, deps, evaluation, structure_cache=structure_cache)
11241133
return s(list(elements))
11251134

1126-
def rewrite_apply_eval_step(self, evaluation) -> Tuple["Expression", bool]:
1135+
def rewrite_apply_eval_step(self, evaluation) -> Tuple[BaseElement, bool]:
11271136
"""Perform a single rewrite/apply/eval step of the bigger
11281137
Expression.evaluate() process.
11291138
@@ -1162,6 +1171,7 @@ def rewrite_apply_eval_step(self, evaluation) -> Tuple["Expression", bool]:
11621171

11631172
if self.elements_properties is None:
11641173
self._build_elements_properties()
1174+
assert self.elements_properties is not None
11651175

11661176
# @timeit
11671177
def eval_elements():
@@ -1804,14 +1814,15 @@ def thread(self, evaluation, head=None) -> Tuple[bool, "Expression"]:
18041814
if head is None:
18051815
head = SymbolList
18061816

1807-
items = []
1817+
prefix: List[BaseElement] = []
1818+
items: List[List[BaseElement]]
18081819
dim = None
18091820
for element in self._elements:
18101821
if element.get_head().sameQ(head):
18111822
if dim is None:
18121823
dim = len(element.get_elements())
18131824
items = [
1814-
(items + [innerelement])
1825+
(prefix + [innerelement])
18151826
for innerelement in element.get_elements()
18161827
]
18171828
elif len(element._elements) != dim:
@@ -1822,7 +1833,7 @@ def thread(self, evaluation, head=None) -> Tuple[bool, "Expression"]:
18221833
items[index].append(element._elements[index])
18231834
else:
18241835
if dim is None:
1825-
items.append(element)
1836+
prefix.append(element)
18261837
else:
18271838
for item in items:
18281839
item.append(element)
@@ -2040,6 +2051,7 @@ def convert_expression_elements(
20402051
elements_properties.is_flat = False
20412052
if converted_elt.elements_properties is None:
20422053
converted_elt._build_elements_properties()
2054+
assert converted_elt.elements_properties is not None
20432055

20442056
if elements_properties.elements_fully_evaluated:
20452057
elements_properties.elements_fully_evaluated = (

0 commit comments

Comments
 (0)