From a29fbf1a02b73468c2d5315f026463154fdf839c Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Tue, 18 Mar 2025 17:22:52 +0100
Subject: [PATCH] added pyupgrade ruff rule and fixed issues

---
 b_asic/GUI/drag_button.py           |  4 +--
 b_asic/GUI/main_window.py           | 21 +++++++--------
 b_asic/architecture.py              | 17 ++++++------
 b_asic/codegen/vhdl/__init__.py     |  6 ++---
 b_asic/codegen/vhdl/architecture.py | 14 +++++-----
 b_asic/codegen/vhdl/common.py       | 42 ++++++++++++++---------------
 b_asic/codegen/vhdl/entity.py       |  4 +--
 b_asic/operation.py                 |  5 ++--
 b_asic/scheduler_gui/main_window.py | 10 +++----
 b_asic/signal_flow_graph.py         | 18 ++++++-------
 b_asic/simulation.py                |  5 +---
 b_asic/types.py                     |  4 +--
 pyproject.toml                      |  4 +--
 test/fixtures/signal_flow_graph.py  |  6 +----
 test/unit/test_architecture.py      |  9 +++----
 test/unit/test_sfg.py               |  4 +--
 16 files changed, 78 insertions(+), 95 deletions(-)

diff --git a/b_asic/GUI/drag_button.py b/b_asic/GUI/drag_button.py
index e628f2a2..7561808b 100644
--- a/b_asic/GUI/drag_button.py
+++ b/b_asic/GUI/drag_button.py
@@ -224,8 +224,8 @@ class DragButton(QPushButton):
         for signal, ports in self._window._arrow_ports.items():
             if any(set(port).intersection(set(self._ports)) for port in ports):
                 self._window._logger.info(
-                    "Removed signal with name: %s to/from operation: %s."
-                    % (signal.signal.name, self.operation.name)
+                    f"Removed signal with name: {signal.signal.name}"
+                    f" to/from operation: {self.operation.name}."
                 )
                 _signals.append(signal)
 
diff --git a/b_asic/GUI/main_window.py b/b_asic/GUI/main_window.py
index 24e48ecf..40dd5310 100644
--- a/b_asic/GUI/main_window.py
+++ b/b_asic/GUI/main_window.py
@@ -12,7 +12,7 @@ import webbrowser
 from collections import deque
 from collections.abc import Sequence
 from types import ModuleType
-from typing import TYPE_CHECKING, Deque, cast
+from typing import TYPE_CHECKING, cast
 
 from qtpy.QtCore import QCoreApplication, QFileInfo, QSettings, QSize, Qt, QThread, Slot
 from qtpy.QtGui import QCursor, QIcon, QKeySequence, QPainter
@@ -81,7 +81,7 @@ class SFGMainWindow(QMainWindow):
         self._scene = QGraphicsScene(self._ui.splitter)
         self._operations_from_name: dict[str, Operation] = {}
         self._zoom = 1
-        self._drag_operation_scenes: dict[DragButton, "QGraphicsProxyWidget"] = {}
+        self._drag_operation_scenes: dict[DragButton, QGraphicsProxyWidget] = {}
         self._drag_buttons: dict[Operation, DragButton] = {}
         self._mouse_pressed = False
         self._mouse_dragging = False
@@ -121,7 +121,7 @@ class SFGMainWindow(QMainWindow):
         # Add operations
         self._max_recent_files = 4
         self._recent_files_actions: list[QAction] = []
-        self._recent_files_paths: Deque[str] = deque(maxlen=self._max_recent_files)
+        self._recent_files_paths: deque[str] = deque(maxlen=self._max_recent_files)
 
         self.add_operations_from_namespace(
             b_asic.core_operations, self._ui.core_operations_list
@@ -488,10 +488,10 @@ class SFGMainWindow(QMainWindow):
             self._logger.warning("Failed to initialize SFG with empty name.")
             return
 
-        self._logger.info("Creating SFG with name: %s from selected operations." % name)
+        self._logger.info(f"Creating SFG with name: {name} from selected operations.")
 
         sfg = SFG(inputs=inputs, outputs=outputs, name=name)
-        self._logger.info("Created SFG with name: %s from selected operations." % name)
+        self._logger.info(f"Created SFG with name: {name} from selected operations.")
         self.update_statusbar(f"Created SFG: {name}")
 
         def check_equality(signal: Signal, signal_2: Signal) -> bool:
@@ -756,7 +756,7 @@ class SFGMainWindow(QMainWindow):
             )
 
     def _create_operation_item(self, item) -> None:
-        self._logger.info("Creating operation of type: %s" % str(item.text()))
+        self._logger.info(f"Creating operation of type: {str(item.text())}")
         try:
             attr_operation = self._operations_from_name[item.text()]()
             self.add_operation(attr_operation)
@@ -840,11 +840,8 @@ class SFGMainWindow(QMainWindow):
             if signal.destination is destination.port
         )
         self._logger.info(
-            "Connecting: %s -> %s."
-            % (
-                source.operation.type_name(),
-                destination.operation.type_name(),
-            )
+            f"Connecting: {source.operation.type_name()}"
+            f" -> {destination.operation.type_name()}."
         )
         try:
             arrow = Arrow(source, destination, self, signal=next(signal_exists))
@@ -906,7 +903,7 @@ class SFGMainWindow(QMainWindow):
         self._thread = {}
         self._sim_worker = {}
         for sfg, properties in self._simulation_dialog._properties.items():
-            self._logger.info("Simulating SFG with name: %s" % str(sfg.name))
+            self._logger.info(f"Simulating SFG with name: {str(sfg.name)}")
             self._sim_worker[sfg] = SimulationWorker(sfg, properties)
             self._thread[sfg] = QThread()
             self._sim_worker[sfg].moveToThread(self._thread[sfg])
diff --git a/b_asic/architecture.py b/b_asic/architecture.py
index 99625a64..81dbed5e 100644
--- a/b_asic/architecture.py
+++ b/b_asic/architecture.py
@@ -8,7 +8,6 @@ from collections.abc import Iterable, Iterator
 from io import TextIOWrapper
 from itertools import chain
 from typing import (
-    DefaultDict,
     Literal,
     cast,
 )
@@ -623,10 +622,10 @@ of :class:`~b_asic.architecture.ProcessingElement`
         )
         self._memories = [memories] if isinstance(memories, Memory) else list(memories)
         self._direct_interconnects = direct_interconnects
-        self._variable_input_port_to_resource: DefaultDict[
+        self._variable_input_port_to_resource: defaultdict[
             InputPort, set[tuple[Resource, int]]
         ] = defaultdict(set)
-        self._variable_outport_to_resource: DefaultDict[
+        self._variable_outport_to_resource: defaultdict[
             OutputPort, set[tuple[Resource, int]]
         ] = defaultdict(set)
         self._operation_input_port_to_resource: dict[InputPort, Resource] = {}
@@ -766,8 +765,8 @@ of :class:`~b_asic.architecture.ProcessingElement`
         if isinstance(mem, str):
             mem = cast(Memory, self.resource_from_name(mem))
 
-        d_in: DefaultDict[Resource, int] = defaultdict(_interconnect_dict)
-        d_out: DefaultDict[Resource, int] = defaultdict(_interconnect_dict)
+        d_in: defaultdict[Resource, int] = defaultdict(_interconnect_dict)
+        d_out: defaultdict[Resource, int] = defaultdict(_interconnect_dict)
         for var in mem.collection:
             var = cast(MemoryVariable, var)
             d_in[self._operation_outport_to_resource[var.write_port]] += 1
@@ -802,10 +801,10 @@ of :class:`~b_asic.architecture.ProcessingElement`
         if isinstance(pe, str):
             pe = cast(ProcessingElement, self.resource_from_name(pe))
 
-        d_in: list[DefaultDict[tuple[Resource, int], int]] = [
+        d_in: list[defaultdict[tuple[Resource, int], int]] = [
             defaultdict(_interconnect_dict) for _ in range(pe.input_count)
         ]
-        d_out: list[DefaultDict[tuple[Resource, int], int]] = [
+        d_out: list[defaultdict[tuple[Resource, int], int]] = [
             defaultdict(_interconnect_dict) for _ in range(pe.output_count)
         ]
         for var in pe.collection:
@@ -1052,8 +1051,8 @@ of :class:`~b_asic.architecture.ProcessingElement`
                 )
 
         # Create list of interconnects
-        edges: DefaultDict[str, set[tuple[str, str]]] = defaultdict(set)
-        destination_edges: DefaultDict[str, set[str]] = defaultdict(set)
+        edges: defaultdict[str, set[tuple[str, str]]] = defaultdict(set)
+        destination_edges: defaultdict[str, set[str]] = defaultdict(set)
         for pe in self._processing_elements:
             inputs, outputs = self.get_interconnects_for_pe(pe)
             for i, inp in enumerate(inputs):
diff --git a/b_asic/codegen/vhdl/__init__.py b/b_asic/codegen/vhdl/__init__.py
index 20345075..f34b60d0 100644
--- a/b_asic/codegen/vhdl/__init__.py
+++ b/b_asic/codegen/vhdl/__init__.py
@@ -2,7 +2,7 @@
 Module for basic VHDL code generation.
 """
 
-from typing import List, Optional, TextIO, Tuple, Union
+from typing import TextIO
 
 # VHDL code generation tab length
 VHDL_TAB = r"    "
@@ -14,7 +14,7 @@ def write(
     text: str,
     *,
     end: str = "\n",
-    start: Optional[str] = None,
+    start: str | None = None,
 ):
     """
     Base VHDL code generation utility.
@@ -42,7 +42,7 @@ def write(
     f.write(f"{VHDL_TAB * indent_level}{text}{end}")
 
 
-def write_lines(f: TextIO, lines: List[Union[Tuple[int, str], Tuple[int, str, str]]]):
+def write_lines(f: TextIO, lines: list[tuple[int, str] | tuple[int, str, str]]):
     """
     Multiline VHDL code generation utility.
 
diff --git a/b_asic/codegen/vhdl/architecture.py b/b_asic/codegen/vhdl/architecture.py
index 524f8c9b..fb548d41 100644
--- a/b_asic/codegen/vhdl/architecture.py
+++ b/b_asic/codegen/vhdl/architecture.py
@@ -3,7 +3,7 @@ Module for code generation of VHDL architectures.
 """
 
 from math import ceil, log2
-from typing import TYPE_CHECKING, Dict, List, Optional, Set, TextIO, Tuple, cast
+from typing import TYPE_CHECKING, TextIO, cast
 
 from b_asic.codegen.vhdl import common, write, write_lines
 from b_asic.process import MemoryVariable
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
 
 def memory_based_storage(
     f: TextIO,
-    assignment: List["ProcessCollection"],
+    assignment: list["ProcessCollection"],
     entity_name: str,
     word_length: int,
     read_ports: int,
@@ -314,7 +314,7 @@ def memory_based_storage(
     write(f, 1, "--", end="\n")
 
     # Extract all the write addresses
-    write_list: List[Optional[Tuple[int, MemoryVariable]]] = [
+    write_list: list[tuple[int, MemoryVariable] | None] = [
         None for _ in range(schedule_time)
     ]
     for i, collection in enumerate(assignment):
@@ -441,7 +441,7 @@ def memory_based_storage(
     write(f, 1, "--", end="\n")
 
     # Extract all the read addresses
-    read_list: List[Optional[Tuple[int, MemoryVariable]]] = [
+    read_list: list[tuple[int, MemoryVariable] | None] = [
         None for _ in range(schedule_time)
     ]
     for i, collection in enumerate(assignment):
@@ -578,15 +578,15 @@ def register_based_storage(
     }
 
     # Table with mapping: register to output multiplexer index
-    output_mux_table: Dict[int, int] = {reg: i for i, reg in enumerate(output_regs)}
+    output_mux_table: dict[int, int] = {reg: i for i, reg in enumerate(output_regs)}
 
     # Back-edge register indices
-    back_edges: Set[Tuple[int, int]] = {
+    back_edges: set[tuple[int, int]] = {
         (frm, to)
         for entry in forward_backward_table
         for frm, to in entry.back_edge_to.items()
     }
-    back_edge_table: Dict[Tuple[int, int], int] = {
+    back_edge_table: dict[tuple[int, int], int] = {
         edge: i + 1 for i, edge in enumerate(back_edges)
     }
 
diff --git a/b_asic/codegen/vhdl/common.py b/b_asic/codegen/vhdl/common.py
index 198eb2d6..d0e7eff0 100644
--- a/b_asic/codegen/vhdl/common.py
+++ b/b_asic/codegen/vhdl/common.py
@@ -5,7 +5,7 @@ Generation of common VHDL constructs
 import re
 from datetime import datetime
 from subprocess import PIPE, Popen
-from typing import Any, Optional, Set, TextIO, Tuple
+from typing import Any, TextIO
 
 from b_asic.codegen.vhdl import write, write_lines
 
@@ -76,10 +76,10 @@ def signal_declaration(
     f: TextIO,
     name: str,
     signal_type: str,
-    default_value: Optional[str] = None,
-    name_pad: Optional[int] = None,
-    vivado_ram_style: Optional[str] = None,
-    quartus_ram_style: Optional[str] = None,
+    default_value: str | None = None,
+    name_pad: int | None = None,
+    vivado_ram_style: str | None = None,
+    quartus_ram_style: str | None = None,
 ):
     """
     Create a VHDL signal declaration.
@@ -137,8 +137,8 @@ def alias_declaration(
     f: TextIO,
     name: str,
     signal_type: str,
-    value: Optional[str] = None,
-    name_pad: Optional[int] = None,
+    value: str | None = None,
+    name_pad: int | None = None,
 ):
     name_pad = name_pad or 0
     write(f, 1, f"alias {name:<{name_pad}} : {signal_type} is {value};")
@@ -149,7 +149,7 @@ def constant_declaration(
     name: str,
     signal_type: str,
     value: Any,
-    name_pad: Optional[int] = None,
+    name_pad: int | None = None,
 ):
     """
     Write a VHDL constant declaration with a name, a type and a value.
@@ -195,7 +195,7 @@ def process_prologue(
     f: TextIO,
     sensitivity_list: str,
     indent: int = 1,
-    name: Optional[str] = None,
+    name: str | None = None,
 ):
     """
     Write the prologue of a regular VHDL process with a user provided sensitivity list.
@@ -222,9 +222,9 @@ def process_prologue(
 
 def process_epilogue(
     f: TextIO,
-    sensitivity_list: Optional[str] = None,
+    sensitivity_list: str | None = None,
     indent: int = 1,
-    name: Optional[str] = None,
+    name: str | None = None,
 ):
     """
     Write the epilogue of a regular VHDL process.
@@ -253,7 +253,7 @@ def synchronous_process_prologue(
     f: TextIO,
     clk: str,
     indent: int = 1,
-    name: Optional[str] = None,
+    name: str | None = None,
 ):
     """
     Write the prologue of a regular VHDL synchronous process with a single clock object.
@@ -280,9 +280,9 @@ def synchronous_process_prologue(
 
 def synchronous_process_epilogue(
     f: TextIO,
-    clk: Optional[str] = None,
+    clk: str | None = None,
     indent: int = 1,
-    name: Optional[str] = None,
+    name: str | None = None,
 ):
     """
     Write the epilogue of a regular VHDL synchronous process with a single clock.
@@ -311,7 +311,7 @@ def synchronous_process(
     clk: str,
     body: str,
     indent: int = 1,
-    name: Optional[str] = None,
+    name: str | None = None,
 ):
     """
     Write a regular VHDL synchronous process with a single clock.
@@ -342,9 +342,9 @@ def synchronous_process(
 def synchronous_memory(
     f: TextIO,
     clk: str,
-    read_ports: Set[Tuple[str, str, str]],
-    write_ports: Set[Tuple[str, str, str]],
-    name: Optional[str] = None,
+    read_ports: set[tuple[str, str, str]],
+    write_ports: set[tuple[str, str, str]],
+    name: str | None = None,
 ):
     """
     Infer a VHDL synchronous reads and writes.
@@ -389,9 +389,9 @@ def synchronous_memory(
 def asynchronous_read_memory(
     f: TextIO,
     clk: str,
-    read_ports: Set[Tuple[str, str, str]],
-    write_ports: Set[Tuple[str, str, str]],
-    name: Optional[str] = None,
+    read_ports: set[tuple[str, str, str]],
+    write_ports: set[tuple[str, str, str]],
+    name: str | None = None,
 ):
     """
     Infer a VHDL memory with synchronous writes and asynchronous reads.
diff --git a/b_asic/codegen/vhdl/entity.py b/b_asic/codegen/vhdl/entity.py
index 9a53e4a4..28a2e6cc 100644
--- a/b_asic/codegen/vhdl/entity.py
+++ b/b_asic/codegen/vhdl/entity.py
@@ -2,7 +2,7 @@
 Module for code generation of VHDL entity declarations
 """
 
-from typing import Set, TextIO
+from typing import TextIO
 
 from b_asic.codegen.vhdl import VHDL_TAB, write_lines
 from b_asic.port import Port
@@ -63,7 +63,7 @@ def memory_based_storage(
         f.write(f"{2 * VHDL_TAB}{port_name} : in std_logic_vector(WL-1 downto 0);\n")
 
     # Write the output port specification
-    write_ports: Set[Port] = {mv.write_port for mv in collection}  # type: ignore
+    write_ports: set[Port] = {mv.write_port for mv in collection}  # type: ignore
     for idx, write_port in enumerate(write_ports):
         port_name = write_port if isinstance(write_port, int) else write_port.name
         port_name = "p_" + str(port_name) + "_out"
diff --git a/b_asic/operation.py b/b_asic/operation.py
index 89977686..d22cacca 100644
--- a/b_asic/operation.py
+++ b/b_asic/operation.py
@@ -13,7 +13,6 @@ from numbers import Number
 from typing import (
     TYPE_CHECKING,
     NewType,
-    Optional,
     cast,
     overload,
 )
@@ -28,8 +27,8 @@ if TYPE_CHECKING:
 
 
 ResultKey = NewType("ResultKey", str)
-ResultMap = Mapping[ResultKey, Optional[Num]]
-MutableResultMap = MutableMapping[ResultKey, Optional[Num]]
+ResultMap = Mapping[ResultKey, Num | None]
+MutableResultMap = MutableMapping[ResultKey, Num | None]
 DelayMap = Mapping[ResultKey, Num]
 MutableDelayMap = MutableMapping[ResultKey, Num]
 
diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py
index 8a86fac7..75bb33f7 100644
--- a/b_asic/scheduler_gui/main_window.py
+++ b/b_asic/scheduler_gui/main_window.py
@@ -15,7 +15,7 @@ import webbrowser
 from collections import defaultdict, deque
 from copy import deepcopy
 from importlib.machinery import SourceFileLoader
-from typing import TYPE_CHECKING, Deque, cast, overload
+from typing import TYPE_CHECKING, cast, overload
 
 # Qt/qtpy
 import qtpy
@@ -142,7 +142,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow):
         # Recent files
         self._max_recent_files = 4
         self._recent_files_actions: list[QAction] = []
-        self._recent_file_paths: Deque[str] = deque(maxlen=self._max_recent_files)
+        self._recent_file_paths: deque[str] = deque(maxlen=self._max_recent_files)
         self._create_recent_file_actions_and_menus()
 
         self._init_graphics()
@@ -349,7 +349,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow):
         except Exception as e:
             log.exception(
                 "Exception occurred. Could not load module from file"
-                " '{}'.\n\n{}".format(abs_path_filename, e)
+                f" '{abs_path_filename}'.\n\n{e}"
             )
             return
 
@@ -368,9 +368,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow):
                 ),
             )
             log.info(
-                "Cannot find any Schedule object in file '{}'.".format(
-                    os.path.basename(abs_path_filename)
-                )
+                f"Cannot find any Schedule object in file '{os.path.basename(abs_path_filename)}'."
             )
             del module
             return
diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py
index 0067a44e..e76da8c0 100644
--- a/b_asic/signal_flow_graph.py
+++ b/b_asic/signal_flow_graph.py
@@ -15,8 +15,6 @@ from math import ceil
 from numbers import Number
 from queue import PriorityQueue
 from typing import (
-    DefaultDict,
-    Deque,
     Optional,
     Union,
     cast,
@@ -41,7 +39,7 @@ from b_asic.types import GraphID, GraphIDNumber, Name, Num, TypeName
 DelayQueue = list[tuple[str, ResultKey, OutputPort]]
 
 
-_OPERATION_SHAPE: DefaultDict[TypeName, str] = defaultdict(lambda: "ellipse")
+_OPERATION_SHAPE: defaultdict[TypeName, str] = defaultdict(lambda: "ellipse")
 _OPERATION_SHAPE.update(
     {
         Input.type_name(): "cds",
@@ -54,7 +52,7 @@ _OPERATION_SHAPE.update(
 class GraphIDGenerator:
     """Generates Graph IDs for objects."""
 
-    _next_id_number: DefaultDict[TypeName, GraphIDNumber]
+    _next_id_number: defaultdict[TypeName, GraphIDNumber]
 
     def __init__(self, id_number_offset: GraphIDNumber = GraphIDNumber(0)):
         """Construct a GraphIDGenerator."""
@@ -112,7 +110,7 @@ class SFG(AbstractOperation):
     """
 
     _components_by_id: dict[GraphID, GraphComponent]
-    _components_by_name: DefaultDict[Name, list[GraphComponent]]
+    _components_by_name: defaultdict[Name, list[GraphComponent]]
     _components_dfs_order: list[GraphComponent]
     _operations_dfs_order: list[Operation]
     _operations_topological_order: list[Operation]
@@ -508,7 +506,7 @@ class SFG(AbstractOperation):
             input_op: index for index, input_op in enumerate(self._input_operations)
         }
         output_op = self._output_operations[output_index]
-        queue: Deque[Operation] = deque([output_op])
+        queue: deque[Operation] = deque([output_op])
         visited: set[Operation] = {output_op}
         while queue:
             op = queue.popleft()
@@ -1116,7 +1114,7 @@ class SFG(AbstractOperation):
         remaining_inports_per_operation = {op: op.input_count for op in self.operations}
 
         # Maps number of input counts to a queue of seen objects with such a size.
-        seen_with_inputs_dict: dict[int, Deque] = defaultdict(deque)
+        seen_with_inputs_dict: dict[int, deque] = defaultdict(deque)
         seen = set()
         top_order = []
 
@@ -1889,7 +1887,7 @@ class SFG(AbstractOperation):
             return []
         for input in inputs_used:
             input_op = self._input_operations[input]
-        queue: Deque[Operation] = deque([input_op])
+        queue: deque[Operation] = deque([input_op])
         visited: set[Operation] = {input_op}
         dict_of_sfg = {}
         while queue:
@@ -1959,7 +1957,7 @@ class SFG(AbstractOperation):
         dict_of_sfg = {}
         for output in output_index_used:
             output_op = self._output_operations[output]
-            queue: Deque[Operation] = deque([output_op])
+            queue: deque[Operation] = deque([output_op])
             visited: set[Operation] = {output_op}
             while queue:
                 op = queue.popleft()
@@ -1975,7 +1973,7 @@ class SFG(AbstractOperation):
                             raise ValueError("Source does not exist")
         for input in input_index_used:
             input_op = self._input_operations[input]
-            queue: Deque[Operation] = deque([input_op])
+            queue: deque[Operation] = deque([input_op])
             visited: set[Operation] = {input_op}
             while queue:
                 op = queue.popleft()
diff --git a/b_asic/simulation.py b/b_asic/simulation.py
index 4b300433..80fe184b 100644
--- a/b_asic/simulation.py
+++ b/b_asic/simulation.py
@@ -7,9 +7,6 @@ Contains a class for simulating the result of an SFG given a set of input values
 from collections import defaultdict
 from collections.abc import Callable, Mapping, MutableMapping, MutableSequence, Sequence
 from numbers import Number
-from typing import (
-    Union,
-)
 
 import numpy as np
 
@@ -20,7 +17,7 @@ from b_asic.types import Num
 ResultArrayMap = Mapping[ResultKey, Sequence[Num]]
 MutableResultArrayMap = MutableMapping[ResultKey, MutableSequence[Num]]
 InputFunction = Callable[[int], Num]
-InputProvider = Union[Num, Sequence[Num], InputFunction]
+InputProvider = Num | Sequence[Num] | InputFunction
 
 
 class Simulation:
diff --git a/b_asic/types.py b/b_asic/types.py
index b141be1c..5dfc6d98 100644
--- a/b_asic/types.py
+++ b/b_asic/types.py
@@ -1,7 +1,7 @@
-from typing import NewType, Union
+from typing import NewType
 
 # https://stackoverflow.com/questions/69334475/how-to-hint-at-number-types-i-e-subclasses-of-number-not-numbers-themselv
-Num = Union[int, float, complex]
+Num = int | float | complex
 
 NumRuntime = (complex, float, int)
 
diff --git a/pyproject.toml b/pyproject.toml
index 1e7aa180..9d44ead1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -100,8 +100,8 @@ precision = 2
 exclude = ["examples"]
 
 [tool.ruff.lint]
-select = ["E4", "E7", "E9", "F", "SIM", "B", "NPY", "C4"]
-ignore = ["F403", "B008", "B021", "B006"]
+select = ["E4", "E7", "E9", "F", "SIM", "B", "NPY", "C4", "UP"]
+ignore = ["F403", "B008", "B021", "B006", "UP038"]
 
 [tool.typos]
 default.extend-identifiers = { ba = "ba", addd0 = "addd0", inout = "inout", ArChItEctUrE = "ArChItEctUrE" }
diff --git a/test/fixtures/signal_flow_graph.py b/test/fixtures/signal_flow_graph.py
index df3e50b0..7764e47f 100644
--- a/test/fixtures/signal_flow_graph.py
+++ b/test/fixtures/signal_flow_graph.py
@@ -1,5 +1,3 @@
-from typing import Optional
-
 import pytest
 
 from b_asic import (
@@ -195,9 +193,7 @@ def sfg_custom_operation():
     """A valid SFG containing a custom operation."""
 
     class CustomOperation(AbstractOperation):
-        def __init__(
-            self, src0: Optional[SignalSourceProvider] = None, name: Name = ""
-        ):
+        def __init__(self, src0: SignalSourceProvider | None = None, name: Name = ""):
             super().__init__(
                 input_count=1, output_count=2, name=name, input_sources=[src0]
             )
diff --git a/test/unit/test_architecture.py b/test/unit/test_architecture.py
index 6ceabe63..e611c048 100644
--- a/test/unit/test_architecture.py
+++ b/test/unit/test_architecture.py
@@ -1,6 +1,5 @@
 import re
 from itertools import chain
-from typing import List
 
 import pytest
 
@@ -89,7 +88,7 @@ def test_architecture(schedule_direct_form_iir_lp_filter: Schedule):
     assert multiplier.entity_name == "multiplier"
     input_pe = ProcessingElement(inputs[0], entity_name="input")
     output_pe = ProcessingElement(outputs[0], entity_name="output")
-    processing_elements: List[ProcessingElement] = [
+    processing_elements: list[ProcessingElement] = [
         adder,
         multiplier,
         input_pe,
@@ -111,7 +110,7 @@ def test_architecture(schedule_direct_form_iir_lp_filter: Schedule):
     direct_conn, mvs = mvs.split_on_length()
 
     # Create Memories from the memory variables
-    memories: List[Memory] = [
+    memories: list[Memory] = [
         Memory(pc) for pc in mvs.split_on_ports(read_ports=1, write_ports=1)
     ]
     assert len(memories) == 1
@@ -198,7 +197,7 @@ def test_move_process(schedule_direct_form_iir_lp_filter: Schedule):
     outputs = operations.get_by_type_name(Output.type_name()).split_on_execution_time()
 
     # Create necessary processing elements
-    processing_elements: List[ProcessingElement] = [
+    processing_elements: list[ProcessingElement] = [
         ProcessingElement(operation, entity_name=f"pe{i}")
         for i, operation in enumerate(chain(adders1, adders2, const_mults))
     ]
@@ -211,7 +210,7 @@ def test_move_process(schedule_direct_form_iir_lp_filter: Schedule):
     direct_conn, mvs = mvs.split_on_length()
 
     # Create Memories from the memory variables (split on length to get two memories)
-    memories: List[Memory] = [Memory(pc) for pc in mvs.split_on_length(6)]
+    memories: list[Memory] = [Memory(pc) for pc in mvs.split_on_length(6)]
 
     # Create architecture
     architecture = Architecture(
diff --git a/test/unit/test_sfg.py b/test/unit/test_sfg.py
index 954c0c82..c5691aaf 100644
--- a/test/unit/test_sfg.py
+++ b/test/unit/test_sfg.py
@@ -4,8 +4,8 @@ import random
 import re
 import string
 import sys
+from collections import Counter
 from os import path, remove
-from typing import Counter, Dict, Type
 
 import numpy as np
 import pytest
@@ -1514,7 +1514,7 @@ class TestCriticalPath:
 
 
 class TestUnfold:
-    def count_kinds(self, sfg: SFG) -> Dict[Type, int]:
+    def count_kinds(self, sfg: SFG) -> dict[type, int]:
         return Counter([type(op) for op in sfg.operations])
 
     # Checks that the number of each kind of operation in sfg2 is multiple*count
-- 
GitLab