diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 6f576fc6..258705f4 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -65,7 +65,7 @@ jobs:
- run: ruff check
- name: Run mypy (only PyQt6 + recent Python)
- run: mypy gitfourchette
+ run: mypy
if: ${{ matrix.qt == 'pyqt6' && matrix.py >= '3.13' }}
- name: Unit tests
diff --git a/gitfourchette/filelists/filelistmodel.py b/gitfourchette/filelists/filelistmodel.py
index cf75a667..d3b3a5c0 100644
--- a/gitfourchette/filelists/filelistmodel.py
+++ b/gitfourchette/filelists/filelistmodel.py
@@ -306,7 +306,7 @@ def matchPathspec(self, pattern: str) -> str:
return next(d.new.path for d in self.deltas if d.new.matchPathspec(pattern))
# Try old side (but return path from *new* side)
- with suppress(StopIteration):
+ with suppress(StopIteration): # type: ignore[unreachable]
return next(d.new.path for d in self.deltas if d.old.matchPathspec(pattern))
return ""
diff --git a/gitfourchette/gitdriver/gitdriver.py b/gitfourchette/gitdriver/gitdriver.py
index 76167d2d..5b06e20c 100644
--- a/gitfourchette/gitdriver/gitdriver.py
+++ b/gitfourchette/gitdriver/gitdriver.py
@@ -323,7 +323,7 @@ def diffFormattingArgs() -> list[str]:
return ["--ignore-all-space"]
if m == WhitespaceMode.IgnoreCrAtEol:
return ["--ignore-cr-at-eol"]
- return []
+ return [] # type: ignore[unreachable]
@classmethod
def buildDiffCommand(
@@ -367,7 +367,7 @@ def buildDiffCommand(
tokens.append(str(b))
else:
- assert delta is None
+ assert delta is None # type: ignore[unreachable]
return tokens
diff --git a/gitfourchette/graph/graphbuilder.py b/gitfourchette/graph/graphbuilder.py
index 3d09ea3d..563648b2 100644
--- a/gitfourchette/graph/graphbuilder.py
+++ b/gitfourchette/graph/graphbuilder.py
@@ -276,7 +276,7 @@ def _stabilizeTrickle(trickle: GraphTrickle, startRow: int, newCommitSequence: l
# See if finished (not too often - expensive)
if ((row & 0xFF) == 0) and trickle.done:
- return row
+ return row # type: ignore[unreachable]
@property
def hiddenCommits(self):
diff --git a/gitfourchette/mainwindow.py b/gitfourchette/mainwindow.py
index 5138ac9f..71cc3e89 100644
--- a/gitfourchette/mainwindow.py
+++ b/gitfourchette/mainwindow.py
@@ -59,7 +59,7 @@ class MainWindow(QMainWindow):
showStatusBarAction: QAction
showMenuBarAction: QAction
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.welcomeStack = QStackedWidget(self)
@@ -137,7 +137,7 @@ def taskRunner(self) -> RepoTaskRunner:
# -------------------------------------------------------------------------
# Event handlers
- def onMouseSideButtonPressed(self, forward: bool):
+ def onMouseSideButtonPressed(self, forward: bool) -> None:
if not self.isActiveWindow():
return
with suppress(NoRepoWidgetError):
@@ -147,17 +147,17 @@ def onMouseSideButtonPressed(self, forward: bool):
else:
repoWidget.navigateBack()
- def onFileDraggedToDockIcon(self, path: str):
+ def onFileDraggedToDockIcon(self, path: str) -> None:
outcome = self.getDropOutcomeFromLocalFilePath(path)
self.handleDrop(*outcome)
- def keyReleaseEvent(self, event: QKeyEvent):
+ def keyReleaseEvent(self, event: QKeyEvent) -> None:
if event.key() == Qt.Key.Key_Alt and self.autoHideMenuBar.enabled:
self.autoHideMenuBar.toggle()
else:
super().keyReleaseEvent(event)
- def dragEnterEvent(self, event: QDragEnterEvent):
+ def dragEnterEvent(self, event: QDragEnterEvent) -> None:
try:
action, data = self.getDropOutcomeFromMimeData(event.mimeData())
if action == DropAction.Deny and not data:
@@ -172,10 +172,10 @@ def dragEnterEvent(self, event: QDragEnterEvent):
logger.exception("dragEnterEvent failed")
event.setAccepted(False)
- def dragLeaveEvent(self, event: QDragLeaveEvent):
+ def dragLeaveEvent(self, event: QDragLeaveEvent) -> None:
self.dropZone.setVisible(False)
- def dropEvent(self, event: QDropEvent):
+ def dropEvent(self, event: QDropEvent) -> None:
self.dropZone.setVisible(False)
action, data = self.getDropOutcomeFromMimeData(event.mimeData())
event.setAccepted(True) # keep dragged item from coming back to cursor on macOS
@@ -184,7 +184,7 @@ def dropEvent(self, event: QDropEvent):
# -------------------------------------------------------------------------
# Menu bar
- def fillGlobalMenuBar(self):
+ def fillGlobalMenuBar(self) -> None:
menubar = self.globalMenuBar
menuObjectNamePrefix = "MWMainMenu"
@@ -412,7 +412,7 @@ def fillGlobalMenuBar(self):
tip=_("Delete all discarded changes from the trash folder")),
)
- def fillRecentMenu(self):
+ def fillRecentMenu(self) -> None:
actions = []
for path in settings.history.getRecentRepoPaths(settings.prefs.maxRecentRepos):
caption = compactPath(path)
@@ -438,7 +438,7 @@ def fillRecentMenu(self):
tip=_("Clear the list of recently opened repositories"),
))
- def onClearRecentMenu(self):
+ def onClearRecentMenu(self) -> None:
settings.history.clearRepoHistory()
settings.history.write()
self.fillRecentMenu()
@@ -466,7 +466,7 @@ def tabWidgetForWorkdirPath(self, workdir: str) -> RepoWidget | RepoStub | None:
return widget
return None
- def setWindowTitle(self, title: str):
+ def setWindowTitle(self, title: str) -> None:
if APP_DEBUG:
chain = ["DEBUG", str(os.getpid()), QT_BINDING]
if APP_TESTMODE:
@@ -510,7 +510,7 @@ def onTabCurrentWidgetChanged(self):
# Refresh the repo
widget.refreshRepo()
- def generateTabContextMenu(self, i: int):
+ def generateTabContextMenu(self, i: int) -> QMenu | None:
if i < 0: # Right mouse button released outside tabs
return None
@@ -534,7 +534,7 @@ def generateTabContextMenu(self, i: int):
return menu
- def onTabContextMenu(self, globalPoint: QPoint, i: int):
+ def onTabContextMenu(self, globalPoint: QPoint, i: int) -> None:
if i < 0: # Right mouse button released outside tabs
return
@@ -542,13 +542,13 @@ def onTabContextMenu(self, globalPoint: QPoint, i: int):
menu.aboutToHide.connect(menu.deleteLater)
menu.popup(globalPoint)
- def onTabMiddleClicked(self, i: int):
+ def onTabMiddleClicked(self, i: int) -> None:
self.onTabSpecialClick(i, "middle")
- def onTabDoubleClicked(self, i: int):
+ def onTabDoubleClicked(self, i: int) -> None:
self.onTabSpecialClick(i, "double")
- def onTabSpecialClick(self, i: int, click: Literal["middle", "double"]):
+ def onTabSpecialClick(self, i: int, click: Literal["middle", "double"]) -> None:
if i < 0:
return
@@ -653,7 +653,7 @@ def _openRepo(self, path: str, foreground=True, tabIndex=-1, exactMatch=True, lo
return stub
- def installRepoWidget(self, rw: RepoWidget, tabIndex: int):
+ def installRepoWidget(self, rw: RepoWidget, tabIndex: int) -> None:
repoStub = self.tabs.widget(tabIndex)
assert tabIndex >= 0, "stub to replace isn't in tabs"
assert isinstance(repoStub, RepoStub), "yanked widget isn't RepoStub"
@@ -677,7 +677,7 @@ def installRepoWidget(self, rw: RepoWidget, tabIndex: int):
repoStub.setParent(None) # tabs don't deparent the widget
repoStub.deleteLater()
- def replaceRepoWidgetWithStub(self, oldWidget: RepoWidget, stub: RepoStub):
+ def replaceRepoWidgetWithStub(self, oldWidget: RepoWidget, stub: RepoStub) -> None:
tabIndex = self.tabs.indexOf(oldWidget)
assert tabIndex >= 0, "RepoWidget to replace isn't in tabs"
self.tabs.swapWidget(tabIndex, stub)
@@ -689,7 +689,7 @@ def replaceRepoWidgetWithStub(self, oldWidget: RepoWidget, stub: RepoStub):
# -------------------------------------------------------------------------
- def onRegainForeground(self):
+ def onRegainForeground(self) -> None:
if QGuiApplication.applicationState() != Qt.ApplicationState.ApplicationActive:
return
if not settings.prefs.autoRefresh:
@@ -697,44 +697,44 @@ def onRegainForeground(self):
with suppress(NoRepoWidgetError):
self.currentRepoWidget().refreshRepo()
- def onRepoNameChanged(self):
+ def onRepoNameChanged(self) -> None:
self.refreshAllTabTexts()
self.fillRecentMenu()
- def onRepoWindowTitleChanged(self, rw: RepoWidget):
+ def onRepoWindowTitleChanged(self, rw: RepoWidget) -> None:
if rw.isVisible():
self.setWindowTitle(rw.windowTitle())
- def onRepoHistoryChanged(self, rw: RepoWidget):
+ def onRepoHistoryChanged(self, rw: RepoWidget) -> None:
if rw.isVisible():
self.mainToolBar.updateNavButtons(rw.navHistory.canGoBack(), rw.navHistory.canGoForward())
- def onRepoRequestsAttention(self, rw: RepoWidget):
+ def onRepoRequestsAttention(self, rw: RepoWidget) -> None:
i = self.tabs.indexOf(rw)
self.tabs.requestAttention(i)
# -------------------------------------------------------------------------
# View menu
- def toggleStatusBar(self):
+ def toggleStatusBar(self) -> None:
GFApplication.applyPrefs(showStatusBar=not settings.prefs.showStatusBar)
- def toggleMenuBar(self):
+ def toggleMenuBar(self) -> None:
GFApplication.applyPrefs(showMenuBar=not settings.prefs.showMenuBar)
- def selectUncommittedChanges(self):
+ def selectUncommittedChanges(self) -> None:
self.currentRepoWidget().jump(NavLocator.inWorkdir())
- def selectHead(self):
+ def selectHead(self) -> None:
self.currentRepoWidget().jump(NavLocator.inRef("HEAD"))
- def focusSidebar(self):
+ def focusSidebar(self) -> None:
self.currentRepoWidget().sidebar.setFocus()
- def focusGraph(self):
+ def focusGraph(self) -> None:
self.currentRepoWidget().graphView.setFocus()
- def focusFiles(self):
+ def focusFiles(self) -> None:
rw = self.currentRepoWidget()
context = rw.navLocator.context
if context == NavContext.COMMITTED:
@@ -747,7 +747,7 @@ def focusFiles(self):
else:
fallback.setFocus()
- def focusDiff(self):
+ def focusDiff(self) -> None:
rw = self.currentRepoWidget()
if rw.specialDiffView.isVisibleTo(rw):
rw.specialDiffView.setFocus()
@@ -756,19 +756,19 @@ def focusDiff(self):
else:
rw.diffView.setFocus()
- def nextFile(self):
+ def nextFile(self) -> None:
self.currentRepoWidget().diffArea.selectNextFile(True)
- def previousFile(self):
+ def previousFile(self) -> None:
self.currentRepoWidget().diffArea.selectNextFile(False)
- def blameFile(self):
+ def blameFile(self) -> None:
self.currentRepoWidget().blameFile()
# -------------------------------------------------------------------------
# Help menu
- def openRescueFolder(self):
+ def openRescueFolder(self) -> None:
trash = Trash.instance()
if trash.exists():
openFolder(str(trash.trashDir))
@@ -778,7 +778,7 @@ def openRescueFolder(self):
_("Open trash folder"),
_("There’s no trash folder. Perhaps you haven’t discarded a change with {0} yet.", qAppName()))
- def clearRescueFolder(self):
+ def clearRescueFolder(self) -> None:
trash = Trash.instance()
sizeOnDisk, patchCount = trash.size()
@@ -809,19 +809,19 @@ def clearRescueFolder(self):
# -------------------------------------------------------------------------
# File menu callbacks
- def newRepo(self):
+ def newRepo(self) -> None:
runner = RepoTaskRunner(self)
runner.ready.connect(runner.deleteLater)
NewRepo.invoke(runner, self.openRepo)
- def cloneDialog(self, initialUrl: str = ""):
+ def cloneDialog(self, initialUrl: str = "") -> None:
dlg = CloneDialog(initialUrl, self)
dlg.cloneSuccessful.connect(lambda path: self.openRepo(path, exactMatch=True))
dlg.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
dlg.setWindowModality(Qt.WindowModality.WindowModal)
dlg.show()
- def openDialog(self):
+ def openDialog(self) -> None:
qfd = PersistentFileDialog.openDirectory(self, "NewRepo", _("Open repository"))
qfd.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) # don't leak dialog
qfd.fileSelected.connect(lambda path: self.openRepo(path, exactMatch=False))
@@ -830,14 +830,14 @@ def openDialog(self):
# -------------------------------------------------------------------------
# Tab management
- def closeCurrentTab(self):
+ def closeCurrentTab(self) -> None:
if self.tabs.count() == 0: # don't attempt to close if no tabs are open
QApplication.beep()
return
self.closeTab(self.tabs.currentIndex())
- def closeTab(self, index: int, finalTab: bool = True):
+ def closeTab(self, index: int, finalTab: bool = True) -> None:
widget = self.tabs.widget(index)
# Remove the tab BEFORE cleaning up the widget
@@ -854,7 +854,7 @@ def closeTab(self, index: int, finalTab: bool = True):
self.saveSession()
gc.collect()
- def closeOtherTabs(self, index: int):
+ def closeOtherTabs(self, index: int) -> None:
# First, set this tab as active so an active tab that gets closed doesn't trigger other tabs to load.
self.tabs.setCurrentIndex(index)
@@ -865,7 +865,7 @@ def closeOtherTabs(self, index: int):
if i != index:
self.closeTab(i, i == final)
- def unloadOtherTabs(self, index: int = -1):
+ def unloadOtherTabs(self, index: int = -1) -> None:
if index < 0:
index = self.tabs.currentIndex()
@@ -888,7 +888,7 @@ def unloadOtherTabs(self, index: int = -1):
self.statusBar2.showMessage(_n("{n} background tab unloaded.", "{n} background tabs unloaded.", numUnloaded))
gc.collect()
- def closeAllTabs(self):
+ def closeAllTabs(self) -> None:
start = self.tabs.count() - 1
with QSignalBlockerContext(self.tabs): # Don't let awaken unloaded tabs
for i in range(start, -1, -1): # Close tabs in reverse order
@@ -896,12 +896,12 @@ def closeAllTabs(self):
self.onTabCurrentWidgetChanged()
- def reloadAllTabs(self):
+ def reloadAllTabs(self) -> None:
self.unloadOtherTabs()
rw = self.currentRepoWidget()
rw.replaceWithStub()
- def refreshAllTabTexts(self):
+ def refreshAllTabTexts(self) -> None:
widgets = list(self.tabs.widgets())
baseTitles = [widget.getTitle() for widget in widgets]
newTitles = baseTitles[:]
@@ -933,7 +933,7 @@ def openRepoNextTo(self, rw, path: str, locator: NavLocator = NavLocator.Empty):
index += 1
return self._openRepo(path, tabIndex=index, exactMatch=True, locator=locator)
- def nextTab(self):
+ def nextTab(self) -> None:
if self.tabs.count() == 0:
QApplication.beep()
return
@@ -942,7 +942,7 @@ def nextTab(self):
index %= self.tabs.count()
self.tabs.setCurrentIndex(index)
- def previousTab(self):
+ def previousTab(self) -> None:
if self.tabs.count() == 0:
QApplication.beep()
return
@@ -954,7 +954,7 @@ def previousTab(self):
# -------------------------------------------------------------------------
# Session management
- def restoreSession(self, session: settings.Session, sloppyPaths: list[str] | None = None):
+ def restoreSession(self, session: settings.Session, sloppyPaths: list[str] | None = None) -> None:
# Note: window geometry, despite being part of the session file, is
# restored in application.py to avoid flashing a window with incorrect
# dimensions on boot
@@ -1016,7 +1016,7 @@ def restoreSession(self, session: settings.Session, sloppyPaths: list[str] | Non
self.tabs.setCurrentIndex(activeTab)
self.onTabCurrentWidgetChanged() # needed to trigger loading on tab #0
- def _reportSessionErrors(self, errors: Sequence[tuple[str, BaseException]]):
+ def _reportSessionErrors(self, errors: Sequence[tuple[str, BaseException]]) -> None:
numErrors = len(errors)
text = _n("The session couldn’t be restored fully because a repository failed to load:",
"The session couldn’t be restored fully because {n} repositories failed to load:", numErrors)
@@ -1024,7 +1024,7 @@ def _reportSessionErrors(self, errors: Sequence[tuple[str, BaseException]]):
addULToMessageBox(qmb, [f"{compactPath(path)}
{exc}" for path, exc in errors])
qmb.show()
- def saveSession(self, writeNow=False):
+ def saveSession(self, writeNow=False) -> None:
session = settings.Session()
session.windowGeometry = self.saveGeometry().data()
session.splitterSizes = RepoWidget.sharedSplitterSizes.copy()
@@ -1034,7 +1034,7 @@ def saveSession(self, writeNow=False):
if writeNow:
session.write()
- def closeEvent(self, event: QCloseEvent):
+ def closeEvent(self, event: QCloseEvent) -> None:
if not GFApplication.instance().mountManager.checkOnClose(self, self.close):
event.setAccepted(False)
return
@@ -1096,7 +1096,7 @@ def getDropOutcomeFromMimeData(self, mime: QMimeData) -> tuple[DropAction, str]:
return DropAction.Deny, ""
- def handleDrop(self, action: DropAction, data: str):
+ def handleDrop(self, action: DropAction, data: str) -> None:
if action == DropAction.Deny:
pass
elif action == DropAction.Clone:
@@ -1112,19 +1112,19 @@ def handleDrop(self, action: DropAction, data: str):
elif action == DropAction.Patch:
tasks.ApplyPatchFile.invoke(self, data)
else:
- warnings.warn(f"Unsupported drag-and-drop outcome {action}")
+ warnings.warn(f"Unsupported drag-and-drop outcome {action}") # type: ignore[unreachable]
# -------------------------------------------------------------------------
# Prefs
- def refreshPrefs(self):
+ def refreshPrefs(self) -> None:
self.statusBar2.setVisible(settings.prefs.showStatusBar)
self.statusBar2.enableMemoryIndicator(APP_DEBUG)
self.mainToolBar.setVisible(settings.prefs.showToolBar)
self.showStatusBarAction.setChecked(settings.prefs.showStatusBar)
self.showMenuBarAction.setChecked(settings.prefs.showMenuBar)
- def onApplyPrefs(self, changedKeys: set[str]):
+ def onApplyPrefs(self, changedKeys: set[str]) -> None:
if "showMenuBar" in changedKeys and not settings.prefs.showMenuBar:
self.showMenuBarHiddenWarning()
@@ -1165,7 +1165,7 @@ def onApplyPrefs(self, changedKeys: set[str]):
# -------------------------------------------------------------------------
# Dispatch commands to detached windows
- def dispatchCloseCommand(self):
+ def dispatchCloseCommand(self) -> None:
if self.isActiveWindow():
self.closeCurrentTab()
return
@@ -1177,7 +1177,7 @@ def dispatchCloseCommand(self):
except KeyError:
QApplication.beep()
- def dispatchSearchCommand(self, op: SearchBar.Op = SearchBar.Op.Start):
+ def dispatchSearchCommand(self, op: SearchBar.Op = SearchBar.Op.Start) -> None:
if self.isActiveWindow() and self.currentRepoWidget():
self.currentRepoWidget().dispatchSearchCommand(op)
return
@@ -1192,7 +1192,7 @@ def dispatchSearchCommand(self, op: SearchBar.Op = SearchBar.Op.Start):
# -------------------------------------------------------------------------
# User commands
- def parseUserCommands(self):
+ def parseUserCommands(self) -> None:
self.userCommands = list(UserCommand.parseCommandBlock(settings.prefs.commands))
def contextualUserCommands(self, *placeholderTokens: UserCommand.Token) -> list[ActionDef]:
@@ -1216,7 +1216,7 @@ def contextualUserCommands(self, *placeholderTokens: UserCommand.Token) -> list[
# Repository-less actions (actions that only need a path to the workdir;
# full-blown RepoWidget not required)
- def repolessActions(self, workdir: str | Callable[[], str]):
+ def repolessActions(self, workdir: str | Callable[[], str]) -> list[ActionDef]:
superprojectLabel = _("Open Superproject")
superprojectEnabled = True
superproject = None
@@ -1284,7 +1284,7 @@ def openRepoInEditor(self, workdir: str) -> None:
ToolProcess.startTextEditor(self, workdir)
- def repolessSetNickname(self, workdir: str):
+ def repolessSetNickname(self, workdir: str) -> None:
defaultName = Path(workdir).name
oldNickname = settings.history.getRepoNickname(workdir, strict=True)
@@ -1306,7 +1306,7 @@ def repolessSetNickname(self, workdir: str):
tid.textAccepted.connect(lambda nick: self._repolessSetNicknameImpl(workdir, nick))
tid.show()
- def _repolessSetNicknameImpl(self, workdir: str, nick: str):
+ def _repolessSetNicknameImpl(self, workdir: str, nick: str) -> None:
settings.history.setRepoNickname(workdir, nick)
settings.history.setDirty()
tabWidget = self.tabWidgetForWorkdirPath(workdir)
@@ -1317,11 +1317,11 @@ def _repolessSetNicknameImpl(self, workdir: str, nick: str):
# RepoStub
self.onRepoNameChanged()
- def repolessCopyPath(self, workdir: str):
+ def repolessCopyPath(self, workdir: str) -> None:
QApplication.clipboard().setText(workdir)
self.statusBar2.showMessage(clipboardStatusMessage(workdir))
- def repolessOpenSuperproject(self, workdir: str, superproject: str | None = None):
+ def repolessOpenSuperproject(self, workdir: str, superproject: str | None = None) -> None:
if superproject is None:
superproject = settings.history.getRepoSuperproject(workdir)
diff --git a/gitfourchette/porcelain.py b/gitfourchette/porcelain.py
index ad047a5d..9df7b70b 100644
--- a/gitfourchette/porcelain.py
+++ b/gitfourchette/porcelain.py
@@ -420,7 +420,7 @@ def is_crud(c: str):
raise NameValidationError(NameValidationError.Rule.CANNOT_BE_EMPTY)
-def signatures_equalish(a: Signature, b: Signature):
+def signatures_equalish(a: Signature, b: Signature) -> bool:
"""
Sometimes two signature objects differ only by the encoding field.
"""
@@ -429,7 +429,7 @@ def signatures_equalish(a: Signature, b: Signature):
return True
if not isinstance(a, Signature) or not isinstance(b, Signature):
- return False
+ return False # type: ignore[unreachable]
if a._encoding == b._encoding:
# If the encodings match and a != b, then the signatures are really different
diff --git a/gitfourchette/prefsfile.py b/gitfourchette/prefsfile.py
index 274ed39f..e32c2d38 100644
--- a/gitfourchette/prefsfile.py
+++ b/gitfourchette/prefsfile.py
@@ -136,7 +136,7 @@ def load(self) -> bool:
return False
if not isinstance(jsonObject, dict):
- logger.warning(f"{prefsPath}: not a JSON dict")
+ logger.warning(f"{prefsPath}: not a JSON dict") # type: ignore[unreachable]
return False
# Pop file version before decoding user fields
@@ -181,7 +181,7 @@ def encode(o: Any) -> Any:
return o
@staticmethod
- def decode(o: Any, dstType: type) -> Any:
+ def decode(o: Any, dstType: type | UnionType | GenericAlias) -> Any:
""" Convert a value coming from a JSON blob to a target type """
construct: typing.Callable[[Any], Any] | None = None
@@ -190,10 +190,12 @@ def decode(o: Any, dstType: type) -> Any:
union = typing.get_args(dstType)
assert len(union) == 2
dstType = next(t for t in union if t is not NoneType)
+ assert type(dstType) is not UnionType
# Extract generic class from GenericAlias, e.g. list[str] --> list
if type(dstType) is GenericAlias:
dstType = typing.get_origin(dstType)
+ assert not isinstance(dstType, GenericAlias)
srcType: type
if dstType is bytes:
diff --git a/gitfourchette/sidebar/sidebar.py b/gitfourchette/sidebar/sidebar.py
index 7ad8e70b..48837fe8 100644
--- a/gitfourchette/sidebar/sidebar.py
+++ b/gitfourchette/sidebar/sidebar.py
@@ -830,7 +830,7 @@ def mouseReleaseEvent(self, event: QMouseEvent):
self.expand(index)
event.accept()
else:
- warnings.warn(f"Unknown click zone {zone}")
+ warnings.warn(f"Unknown click zone {zone}") # type: ignore[unreachable]
def mouseDoubleClickEvent(self, event: QMouseEvent):
# NOT calling "super().mouseDoubleClickEvent(event)" on purpose.
diff --git a/gitfourchette/syntax/lexercache.py b/gitfourchette/syntax/lexercache.py
index a3c3d671..d60f1ae5 100644
--- a/gitfourchette/syntax/lexercache.py
+++ b/gitfourchette/syntax/lexercache.py
@@ -9,7 +9,6 @@
import logging
import os.path
import re
-from contextlib import suppress
from typing import ClassVar
import pygments.lexers
@@ -105,8 +104,9 @@ def getLexerFromPath(cls, path: str, allowPlugins: bool) -> Lexer | None:
return None
# Get existing lexer instance
- with suppress(KeyError):
- return cls.lexerInstances[alias]
+ instance = cls.lexerInstances.get(alias)
+ if instance is not None:
+ return instance
# Instantiate new lexer.
# Notes:
@@ -120,7 +120,7 @@ def getLexerFromPath(cls, path: str, allowPlugins: bool) -> Lexer | None:
@classmethod
@benchmark
- def warmUp(cls, allowPlugins: bool):
+ def warmUp(cls, allowPlugins: bool) -> None:
"""
Cache lexerAliases (map of filename patterns to lexer names).
Turn off plugins for a significant speedup.
@@ -169,7 +169,7 @@ def warmUp(cls, allowPlugins: bool):
patternsToLexers = {}
for lexerName, patternSet in lexers.items():
if False: # pragma: no cover - replace with 'if True' to debug missing disambiguations
- overwrite = patternSet.intersection(patternsToLexers)
+ overwrite = patternSet.intersection(patternsToLexers) # type: ignore[unreachable]
if overwrite:
logger.warning(f"Missing disambiguation: Lexer '{lexerName}' will take precedence over { { p: patternsToLexers[p] for p in overwrite } }")
diff --git a/gitfourchette/toolbox/gitutils.py b/gitfourchette/toolbox/gitutils.py
index c03060aa..0992d77a 100644
--- a/gitfourchette/toolbox/gitutils.py
+++ b/gitfourchette/toolbox/gitutils.py
@@ -89,7 +89,7 @@ def abbreviatePerson(sig: Signature, style: AuthorDisplayStyle = AuthorDisplaySt
else:
return emailParts[0]
- return sig.email
+ return sig.email # type: ignore[unreachable]
def shortHash(oid: Oid | str) -> str:
diff --git a/pyproject.toml b/pyproject.toml
index b42ca76e..6d803a01 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -177,9 +177,19 @@ ignore = [
# Type checking
[tool.mypy]
+files = [
+ "gitfourchette",
+ #"test",
+]
+show_column_numbers = true
+show_error_codes = true
+show_traceback = true
+local_partial_types = true
strict_optional = false
check_untyped_defs = true
disallow_untyped_decorators = true
+warn_unreachable = true
+#warn_unused_ignores = true
[[tool.mypy.overrides]]
module = ["mfusepy", "psutil"]