Lint Python tools in CI with a checker-specific baseline ratchet (#323)

Add pinned ruff and pyright checks, the existing-findings baseline, and a
workflow that rejects new findings and requires fixed findings to be removed.

Compare checker pins numerically and permit baseline growth only for the
checker whose release version increased. Comments, unrelated dependency
edits and downgrades retain the guard. Add 10 regression tests using Git
trees and document the policy.

Validation: 14 guard/documentation tests pass; pinned lint reports 414
existing findings, zero new findings and zero stale baseline entries.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
This commit is contained in:
seeddsandv0lumehi committed 2026-10-27 02:57:38 +08:00
1 parent ed3007f1ef
commit 7f5ef4155e
7 files changed
+600

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
# The Python lint job's tools (.github/workflows/lint-python.yml, dm/tools/lint_python.py), pinned: their findings change with their versions, so raising one
# goes with `python3 dm/tools/lint_python.py --rebaseline` in the same commit. The last three are the packages the tools import (as in tests.yml), so pyright resolves them.
ruff==0.17.2
pyright==1.1.414
lz4==4.4.5
capstone==5.0.9
fonttools==4.67.0
+48
View File
@@ -0,0 +1,48 @@
# Python lint and types over scripts/ and the tools under dm/: ruff (lint rules for bugs, never formatting) and pyright in basic mode, configured in pyproject.toml.
# A ratchet: the findings the code had when this came in are in dm/tools/lint_python_baseline.tsv, so the job fails only on a new finding, and on a fixed one still
# listed there (`python3 dm/tools/lint_python.py --update` shrinks it). On a pull request the baseline may not grow over the base branch's either.
# ty (Astral's type checker, beta) runs beside it as advisory: its findings go to the job summary and never fail the build.
# The tool versions are pinned in .github/requirements/lint.txt; docs/development.md has the commands.
name: lint-python
on:
pull_request:
paths: ['**.py', pyproject.toml, .github/requirements/lint.txt, .github/workflows/lint-python.yml, dm/tools/lint_python_baseline.tsv]
push:
branches: [main]
paths: ['**.py', pyproject.toml, .github/requirements/lint.txt, .github/workflows/lint-python.yml, dm/tools/lint_python_baseline.tsv]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: lint-python-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
# Actions are pinned to commit SHAs (the tag in the comment), as in tests.yml.
jobs:
lint:
name: ruff and pyright (ratchet)
runs-on: ubuntu-24.04 # pyproject.toml fixes the platform pyright assumes (Darwin), so the findings match a Mac's
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: '3.14'
cache: pip
cache-dependency-path: .github/requirements/lint.txt
- name: Install the pinned tools
run: python -m pip install -r .github/requirements/lint.txt
- name: Base branch's baseline (pull requests)
if: github.event_name == 'pull_request'
run: git fetch --no-tags --depth=1 origin "$GITHUB_BASE_REF"
- name: ruff and pyright against the baseline
run: python dm/tools/lint_python.py ${{ github.event_name == 'pull_request' && '--against FETCH_HEAD' || '' }}
+130
View File
@@ -0,0 +1,130 @@
#!/usr/bin/env python3
# Python lint and type check with a ratchet (.github/workflows/lint-python.yml): ruff (pyproject.toml's [tool.ruff]: lint rules for bugs, no formatting) and pyright in basic
# mode ([tool.pyright]) over scripts/ and the tools under dm/. The findings the code had when the check came in are listed in dm/tools/lint_python_baseline.tsv, keyed by tool,
# file, rule and message with a count (no line numbers: they survive edits elsewhere in the file). The check fails on a finding beyond its key's count (a new one) and on a
# count above what the code has now (a fixed finding still listed: run --update). So the baseline only shrinks:
# --update lowers counts to what the code has now and drops fixed entries; it never adds one (a new finding is fixed, or silenced on its line with a noqa or
# `# pyright: ignore[<rule>]` and a reason)
# --rebaseline writes every current finding: only with a raised tool version in .github/requirements/lint.txt, whose findings change with it
# --against REV fails when the baseline has an entry or a count that REV's has not, unless that checker's pinned version increased (CI uses the PR's base)
# Run it with the pinned tools, so the findings match CI's (other versions or other packages in the environment change them):
# python3 -m venv <dir> && <dir>/bin/pip install -r .github/requirements/lint.txt && <dir>/bin/python3 dm/tools/lint_python.py [--update]
# Under GitHub Actions, new findings are annotated on their lines and a summary goes to $GITHUB_STEP_SUMMARY.
import argparse, collections, json, os, re, subprocess, sys, pathlib
REPO = pathlib.Path(__file__).resolve().parents[2]
BASELINE = REPO / 'dm/tools/lint_python_baseline.tsv'
REQS = '.github/requirements/lint.txt'
HEADER = '# tool\tfile\trule\tcount\tmessage (dm/tools/lint_python.py; shrink with --update, never edit by hand to add)\n'
def rel(p): return pathlib.Path(p).resolve().relative_to(REPO).as_posix()
def norm(msg): return re.sub(r'\s+', ' ', msg).strip() # pyright's messages span lines and use no-break spaces
def run(cmd):
r = subprocess.run(cmd, cwd=REPO, capture_output=True, text=True)
if not r.stdout.strip(): sys.exit(f'[RED] lint_python: {" ".join(cmd[:3])} printed nothing (exit {r.returncode}):\n{r.stderr}')
return r.stdout
def ruff():
out = json.loads(run([sys.executable, '-m', 'ruff', 'check', '--no-cache', '--exit-zero', '--output-format', 'json', '.']))
return [('ruff', rel(f['filename']), f['code'] or 'syntax', norm(f['message']), f['location']['row']) for f in out]
def pyright():
out = json.loads(run([sys.executable, '-m', 'pyright', '--outputjson', '--pythonpath', sys.executable]))
return [('pyright', rel(d['file']), d.get('rule') or d['severity'], norm(d['message']), d['range']['start']['line'] + 1)
for d in out['generalDiagnostics'] if d['severity'] in ('error', 'warning')]
def load(text):
base = collections.Counter()
for line in text.splitlines():
if not line or line.startswith('#'): continue
tool, path, rule, n, msg = line.split('\t', 4)
base[(tool, path, rule, msg)] = int(n)
return base
def write(base):
rows = sorted(f'{k[0]}\t{k[1]}\t{k[2]}\t{n}\t{k[3]}\n' for k, n in base.items() if n > 0)
BASELINE.write_text(HEADER + ''.join(rows))
def git(*a): return subprocess.run(['git', *a], cwd=REPO, capture_output=True, text=True)
def versions(text):
# The checkers use three-part release pins. Reject missing, duplicate or unsupported pins instead of authorizing a baseline rewrite.
pins = {}
for line in text.splitlines():
name, _, version = line.split('#', 1)[0].strip().partition('==')
name, version = name.strip().lower(), version.strip()
if name not in ('ruff', 'pyright'): continue
if name in pins or not re.fullmatch(r'\d+\.\d+\.\d+', version):
raise ValueError(f'{REQS}: {name} must have one numeric release pin (name==X.Y.Z)')
pins[name] = tuple(map(int, version.split('.')))
if set(pins) != {'ruff', 'pyright'}: raise ValueError(f'{REQS}: both ruff and pyright need numeric release pins (name==X.Y.Z)')
return pins
def against(rev, base):
# Only an upgraded checker may gain entries or counts; dependency edits and downgrades do not change the ratchet.
old = git('show', f'{rev}:{rel(BASELINE)}')
if old.returncode: print(f'lint_python: no baseline at {rev}: nothing to compare'); return 0
old_reqs = git('show', f'{rev}:{REQS}')
if old_reqs.returncode: print(f'[RED] lint_python: cannot read {REQS} at {rev}: {old_reqs.stderr.strip()}'); return 1
try:
before, after = versions(old_reqs.stdout), versions((REPO / REQS).read_text())
except (OSError, ValueError) as e:
print(f'[RED] lint_python: {e}'); return 1
upgraded = {t for t in before if after[t] > before[t]}
for t in sorted(upgraded): print(f'lint_python: {t} upgraded since {rev}: its baseline may be rewritten')
was = load(old.stdout)
grown = [(k, n, was[k]) for k, n in base.items() if n > was[k] and k[0] not in upgraded]
for (tool, path, rule, msg), n, w in grown: print(f'::error file={rel(BASELINE)}::baseline grew: {tool} {path} {rule} {w} -> {n}: {msg}')
if grown: print(f'lint_python: the baseline gained {len(grown)} entries over {rev}: fix the new findings instead (it only shrinks)')
return 1 if grown else 0
def summary(md):
if os.environ.get('GITHUB_STEP_SUMMARY'):
with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as f: f.write(md + '\n')
def check(args):
found = ruff() + pyright()
cur = collections.Counter(f[:4] for f in found)
base = load(BASELINE.read_text()) if BASELINE.exists() else collections.Counter()
if args.rebaseline:
write(cur); print(f'lint_python: wrote {sum(cur.values())} findings to {rel(BASELINE)}'); return 0
new = {k: n - base[k] for k, n in cur.items() if n > base[k]}
fixed = {k: base[k] - cur[k] for k in base if base[k] > cur[k]}
if args.update:
shrunk = collections.Counter({k: min(n, cur[k]) for k, n in base.items()})
write(shrunk); print(f'lint_python: baseline {sum(base.values())} -> {sum(shrunk.values())} findings')
base, fixed = shrunk, {}
gha = os.environ.get('GITHUB_ACTIONS') == 'true'
for tool, path, rule, msg, line in found:
k = (tool, path, rule, msg)
if new.get(k): # with several of one key, every line is shown: which one is new is not known
print(f'::error file={path},line={line},title={tool} {rule}::{msg}' if gha else f'{path}:{line}: {tool} {rule}: {msg}')
for (tool, path, rule, msg), n in sorted(fixed.items()):
print(f'fixed, still in the baseline: {tool} {path} {rule} x{n}: {msg}')
n_new, n_fixed = sum(new.values()), sum(fixed.values())
rows = ['| tool | findings | in the baseline | new | fixed, still listed |', '|---|---:|---:|---:|---:|']
for t in ('ruff', 'pyright'):
rows.append(f'| {t} | {sum(n for k, n in cur.items() if k[0] == t)} | {sum(n for k, n in base.items() if k[0] == t)} '
f'| {sum(n for k, n in new.items() if k[0] == t)} | {sum(n for k, n in fixed.items() if k[0] == t)} |')
verdict = ('New findings: fix them (or silence one on its line with `# noqa: <rule>` / `# pyright: ignore[<rule>]` and a reason).' if n_new else
'Fixed findings are still in the baseline: run `python3 dm/tools/lint_python.py --update` and commit it.' if n_fixed else
'No new findings.')
summary('### Python lint and types (ruff, pyright basic)\n\n' + '\n'.join(rows) + f'\n\n{verdict}\n')
print(f'lint_python: {sum(cur.values())} findings, {sum(base.values())} in the baseline, {n_new} new, {n_fixed} fixed but still listed')
if n_fixed: print('lint_python: run python3 dm/tools/lint_python.py --update to shrink the baseline')
rc = 1 if n_new or n_fixed else 0
if args.against: rc |= against(args.against, load(BASELINE.read_text()) if BASELINE.exists() else collections.Counter())
return rc
def main():
ap = argparse.ArgumentParser(description='ruff and pyright with a ratchet baseline')
g = ap.add_mutually_exclusive_group()
g.add_argument('--update', action='store_true', help='shrink the baseline to the findings the code still has')
g.add_argument('--rebaseline', action='store_true', help='write every current finding (only after raising a pinned tool version)')
ap.add_argument('--against', metavar='REV', help='also fail when the baseline grew over the one at REV')
args = ap.parse_args()
return check(args)
if __name__ == '__main__':
sys.exit(main())
+257
View File
@@ -0,0 +1,257 @@
# tool file rule count message (dm/tools/lint_python.py; shrink with --update, never edit by hand to add)
pyright dm/games/profile_guess.py reportArgumentType 1 Argument of type "set[Unknown]" cannot be assigned to parameter "nro_imports" of type "frozenset[Unknown]" in function "shader_sources" "set[Unknown]" is not assignable to "frozenset[Unknown]"
pyright dm/gfx/tools/c1_guard.py reportOptionalMemberAccess 1 "startswith" is not a known attribute of "None"
pyright dm/gfx/tools/c1_guard.py reportOptionalOperand 1 Operator "+" not supported for "None"
pyright dm/gfx/tools/c1_guard.py reportOptionalOperand 1 Operator "-" not supported for "None"
pyright dm/gfx/tools/c1_guard.py reportOptionalOperand 2 Operator ">=" not supported for "None"
pyright dm/gfx/tools/cb_extent.py reportArgumentType 1 Argument of type "Unknown | None" cannot be assigned to parameter "arg2" of type "SupportsRichComparisonT@max" in function "max" Type "Unknown | None" is not assignable to type "SupportsRichComparison" Type "Unknown | None" is not assignable to type "SupportsRichComparison" Type "None" is not assignable to type "SupportsRichComparison" "None" is incompatible with protocol "SupportsDunderLT[Any]" "__lt__" is not present "None" is incompatible with protocol "SupportsDunderGT[Any]" "__gt__" is not present
pyright dm/gfx/tools/cb_extent.py reportArgumentType 1 Argument of type "Unknown | None" cannot be assigned to parameter "arg2" of type "SupportsRichComparisonT@max" in function "max" Type "Unknown | None" is not assignable to type "int" "None" is not assignable to "int"
pyright dm/gfx/tools/cb_extent.py reportOptionalOperand 1 Operator "<" not supported for "None"
pyright dm/gfx/tools/dmsh_build.py reportArgumentType 1 Argument of type "str | None" cannot be assigned to parameter "file" of type "FileDescriptorOrPath" in function "open" Type "str | None" is not assignable to type "FileDescriptorOrPath" Type "None" is not assignable to type "FileDescriptorOrPath" "None" is not assignable to "int" "None" is not assignable to "str" "None" is not assignable to "bytes" "None" is incompatible with protocol "PathLike[str]" "__fspath__" is not present "None" is incompatible with protocol "PathLike[bytes]" ...
pyright dm/gfx/tools/dmsh_build.py reportArgumentType 3 Argument of type "str | None" cannot be assigned to parameter "a" of type "BytesPath" in function "join" Type "str | None" is not assignable to type "BytesPath" Type "str" is not assignable to type "BytesPath" "str" is not assignable to "bytes" "str" is incompatible with protocol "PathLike[bytes]" "__fspath__" is not present
pyright dm/gfx/tools/dmsh_build.py reportAttributeAccessIssue 1 Cannot access attribute "items" for class "str" Attribute "items" is unknown
pyright dm/gfx/tools/dmsh_build.py reportAttributeAccessIssue 1 Cannot access attribute "lineno" for class "AST" Attribute "lineno" is unknown
pyright dm/gfx/tools/dmsh_build.py reportCallIssue 1 No overloads for "open" match the provided arguments
pyright dm/gfx/tools/dmsh_build.py reportCallIssue 3 No overloads for "join" match the provided arguments
pyright dm/gfx/tools/dmsh_build.py reportGeneralTypeIssues 2 "None" is not iterable "__iter__" method not defined
pyright dm/gfx/tools/dmsh_build.py reportOperatorIssue 1 Operator "+" not supported for types "Literal[128]" and "int | None" Operator "+" not supported for types "Literal[128]" and "None" when expected type is "int | Unknown"
pyright dm/gfx/tools/dmsh_build.py reportOptionalMemberAccess 4 "group" is not a known attribute of "None"
pyright dm/gfx/tools/dmsh_build.py reportOptionalSubscript 13 Object of type "None" is not subscriptable
pyright dm/gfx/tools/gl_shaders.py reportArgumentType 1 Argument of type "list[str]" cannot be assigned to parameter "iterable" of type "Iterable[LiteralString]" in function "join" "list[str]" is not assignable to "Iterable[LiteralString]" Type parameter "_T_co@Iterable" is covariant, but "str" is not a subtype of "LiteralString" "str" is not assignable to "LiteralString"
pyright dm/gfx/tools/gl_shaders.py reportCallIssue 1 No overloads for "join" match the provided arguments
pyright dm/gfx/tools/own_compilers.py reportOptionalSubscript 3 Object of type "None" is not subscriptable
pyright dm/gfx/tools/shader_split.py reportArgumentType 1 Argument of type "Literal['m_Name']" cannot be assigned to parameter "s" of type "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" in function "__getitem__" "Literal['m_Name']" is not assignable to "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]"
pyright dm/gfx/tools/shader_split.py reportArgumentType 1 Argument of type "Literal['m_ParsedForm']" cannot be assigned to parameter "s" of type "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" in function "__getitem__" "Literal['m_ParsedForm']" is not assignable to "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]"
pyright dm/gfx/tools/shader_split.py reportArgumentType 1 Argument of type "Literal['variants']" cannot be assigned to parameter "s" of type "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" in function "__getitem__" "Literal['variants']" is not assignable to "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]"
pyright dm/gfx/tools/shader_split.py reportArgumentType 1 Argument of type "bytes | None" cannot be assigned to parameter "buffer" of type "ReadableBuffer" in function "unpack_from" Type "bytes | None" is not assignable to type "ReadableBuffer" "None" is incompatible with protocol "Buffer" "__buffer__" is not present
pyright dm/gfx/tools/shader_split.py reportArgumentType 1 Argument of type "list[str | None]" cannot be assigned to parameter "args" of type "_CMD" in function "__init__" Type "str | None" is not assignable to type "StrOrBytesPath" Type "None" is not assignable to type "StrOrBytesPath" "None" is not assignable to "str" "None" is not assignable to "bytes" "None" is incompatible with protocol "PathLike[str]" "__fspath__" is not present "None" is incompatible with protocol "PathLike[bytes]" "__fspath__" is not present
pyright dm/gfx/tools/shader_split.py reportArgumentType 2 Argument of type "Unknown | int | None" cannot be assigned to parameter "offset" of type "int" in function "unpack_from" Type "Unknown | int | None" is not assignable to type "int" "None" is not assignable to "int"
pyright dm/gfx/tools/shader_split.py reportAssignmentType 1 Expression with type "tuple[Any, Any] | Any" cannot be assigned to target tuple Type "tuple[Any, Any]" is incompatible with target tuple Tuple size mismatch; expected 4 but received 2
pyright dm/gfx/tools/shader_split.py reportAttributeAccessIssue 1 Cannot access attribute "get" for class "list[Any]" Attribute "get" is unknown
pyright dm/gfx/tools/shader_split.py reportCallIssue 1 No overloads for "__init__" match the provided arguments
pyright dm/gfx/tools/shader_split.py reportCallIssue 3 No overloads for "__getitem__" match the provided arguments
pyright dm/gfx/tools/shader_split.py reportGeneralTypeIssues 2 "None" is not iterable "__iter__" method not defined
pyright dm/gfx/tools/shader_split.py reportMissingImports 1 Import "backports" could not be resolved
pyright dm/gfx/tools/shader_split.py reportOperatorIssue 1 Operator "<=" not supported for types "int" and "Any | tuple[Any, ...]" Operator "<=" not supported for types "int" and "tuple[Any, ...]"
pyright dm/gfx/tools/shader_split.py reportOptionalIterable 5 Object of type "None" cannot be used as iterable value
pyright dm/gfx/tools/shader_split.py reportOptionalMemberAccess 1 "flush" is not a known attribute of "None"
pyright dm/gfx/tools/shader_split.py reportOptionalMemberAccess 2 "read" is not a known attribute of "None"
pyright dm/gfx/tools/shader_split.py reportOptionalMemberAccess 2 "write" is not a known attribute of "None"
pyright dm/gfx/tools/shader_split.py reportOptionalSubscript 1 Object of type "None" is not subscriptable
pyright dm/gfx/tools/shader_split.py reportSelfClsParameterName 5 Instance methods should take a "self" parameter
pyright dm/gfx/tools/smash_arc.py reportMissingImports 1 Import "backports" could not be resolved
pyright dm/gfx/tools/uepak.py reportArgumentType 9 Argument of type "tuple[Any, Any, *tuple[Any, ...]] | Any" cannot be assigned to parameter "stop" of type "SupportsIndex" in function "__new__" Type "tuple[Any, Any, *tuple[Any, ...]] | Any" is not assignable to type "SupportsIndex" "tuple[Any, Any, *tuple[Any, ...]]" is incompatible with protocol "SupportsIndex" "__index__" is not present
pyright dm/gfx/tools/uepak.py reportAssignmentType 4 Expression with type "tuple[Any, Any] | Any" cannot be assigned to target tuple Type "tuple[Any, Any]" is incompatible with target tuple Tuple size mismatch; expected 4 but received 2
pyright dm/gfx/tools/uepak.py reportGeneralTypeIssues 1 Index 0 is out of range for type tuple[()]
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator "*" not supported for types "list[None]" and "tuple[Any, Any, *tuple[Any, ...]] | Any" Operator "*" not supported for types "list[None]" and "tuple[Any, Any, *tuple[Any, ...]]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator "+" not supported for types "Unknown | tuple[Any, Any, *tuple[Any, ...]] | Any" and "Literal[53]" Operator "+" not supported for types "tuple[Any, Any, *tuple[Any, ...]]" and "Literal[53]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator "+" not supported for types "tuple[Any, Any, *tuple[Any, ...]] | Any" and "Literal[15]" Operator "+" not supported for types "tuple[Any, Any, *tuple[Any, ...]]" and "Literal[15]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator "+=" not supported for types "Unknown | Any | int" and "Unknown | Any | tuple[Any, Any, *tuple[Any, ...]]" Operator "+" not supported for types "int" and "tuple[Any, Any, *tuple[Any, ...]]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator "-=" not supported for types "Unknown | tuple[Any, Any, *tuple[Any, ...]] | Any" and "Unknown | tuple[Any, Any, *tuple[Any, ...]] | Any" Operator "-" not supported for types "tuple[Any, Any, *tuple[Any, ...]]" and "tuple[Any, Any, *tuple[Any, ...]]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator "<" not supported for types "tuple[Any, Any, *tuple[Any, ...]] | Any" and "Literal[0]" Operator "<" not supported for types "tuple[Any, Any, *tuple[Any, ...]]" and "Literal[0]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]] | Any" and "Literal[22]" Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]]" and "Literal[22]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]] | Any" and "Literal[23]" Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]]" and "Literal[23]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]] | Any" and "Literal[29]" Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]]" and "Literal[29]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]] | Any" and "Literal[30]" Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]]" and "Literal[30]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]] | Any" and "Literal[31]" Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]]" and "Literal[31]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 1 Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]] | Any" and "Literal[6]" Operator ">>" not supported for types "tuple[Any, Any, *tuple[Any, ...]]" and "Literal[6]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 2 Operator "&" not supported for types "tuple[Any, Any, *tuple[Any, ...]] | Any" and "Literal[63]" Operator "&" not supported for types "tuple[Any, Any, *tuple[Any, ...]]" and "Literal[63]"
pyright dm/gfx/tools/uepak.py reportOperatorIssue 2 Operator "+=" not supported for types "int | Unknown" and "tuple[Any, ...] | Any" Operator "+" not supported for types "int" and "tuple[Any, ...]"
pyright dm/gfx/tools/uepak.py reportOptionalMemberAccess 1 "close" is not a known attribute of "None"
pyright dm/gfx/tools/uepak.py reportOptionalMemberAccess 1 "flush" is not a known attribute of "None"
pyright dm/gfx/tools/uepak.py reportOptionalMemberAccess 2 "read" is not a known attribute of "None"
pyright dm/gfx/tools/uepak.py reportOptionalMemberAccess 2 "write" is not a known attribute of "None"
pyright dm/gfx/tools/unityfs.py reportArgumentType 4 Argument of type "Any | tuple[Any, ...]" cannot be assigned to parameter "stop" of type "SupportsIndex" in function "__new__" Type "Any | tuple[Any, ...]" is not assignable to type "SupportsIndex" "tuple[Any, ...]" is incompatible with protocol "SupportsIndex" "__index__" is not present
pyright dm/gfx/tools/unityfs.py reportOperatorIssue 1 Operator "+" not supported for types "int | Any" and "Any | tuple[Any, ...]" Operator "+" not supported for types "int" and "tuple[Any, ...]"
pyright dm/gfx/tools/unityfs.py reportOperatorIssue 1 Operator "+=" not supported for types "int | Any" and "Any | tuple[Any, ...]" Operator "+" not supported for types "int" and "tuple[Any, ...]"
pyright dm/gfx/tools/unityfs.py reportOperatorIssue 1 Operator "<" not supported for types "Any | tuple[Any, ...]" and "Literal[202012090]" Operator "<" not supported for types "tuple[Any, ...]" and "Literal[202012090]"
pyright dm/gfx/tools/unityfs.py reportOperatorIssue 1 Operator "<=" not supported for types "Any | tuple[Any, ...]" and "int" Operator "<=" not supported for types "tuple[Any, ...]" and "int"
pyright dm/gfx/tools/unityfs.py reportOperatorIssue 1 Operator "<=" not supported for types "Literal[201806140]" and "Any | tuple[Any, ...]" Operator "<=" not supported for types "Literal[201806140]" and "tuple[Any, ...]"
pyright dm/gfx/tools/unityfs.py reportOperatorIssue 1 Operator ">=" not supported for types "Any | tuple[Any, ...]" and "Literal[201608170]" Operator ">=" not supported for types "tuple[Any, ...]" and "Literal[201608170]"
pyright dm/gfx/tools/unityfs.py reportOperatorIssue 2 Operator "+" not supported for types "int" and "Any | tuple[Any, ...]" Operator "+" not supported for types "int" and "tuple[Any, ...]"
pyright dm/gfx/tools/unityfs.py reportOperatorIssue 2 Operator "+=" not supported for types "int" and "Any | tuple[Any, ...]" Operator "+" not supported for types "int" and "tuple[Any, ...]"
pyright dm/gfx/tools/unityfs.py reportOperatorIssue 2 Operator "<=" not supported for types "Literal[0]" and "Any | tuple[Any, ...]" Operator "<=" not supported for types "Literal[0]" and "tuple[Any, ...]"
pyright dm/gfx/tools/unityfs.py reportSelfClsParameterName 4 Instance methods should take a "self" parameter
pyright dm/gfx/tools/vk_shaders.py reportArgumentType 1 Argument of type "list[str]" cannot be assigned to parameter "iterable" of type "Iterable[LiteralString]" in function "join" "list[str]" is not assignable to "Iterable[LiteralString]" Type parameter "_T_co@Iterable" is covariant, but "str" is not a subtype of "LiteralString" "str" is not assignable to "LiteralString"
pyright dm/gfx/tools/vk_shaders.py reportCallIssue 1 No overloads for "join" match the provided arguments
pyright dm/gfx/tools/vk_shaders.py reportOptionalMemberAccess 2 "group" is not a known attribute of "None"
pyright dm/gfx/tools/vk_shaders.py reportOptionalMemberAccess 2 "start" is not a known attribute of "None"
pyright dm/ios/tools/pack_ipa.py reportCallIssue 1 Expected 4 positional arguments
pyright dm/ios/tools/xcode_projects.py reportArgumentType 1 Argument of type "Element[str] | None" cannot be assigned to parameter "parent" of type "Element[Any]" in function "SubElement" Type "Element[str] | None" is not assignable to type "Element[Any]" "None" is not assignable to "Element[Any]"
pyright dm/ios/tools/xcode_projects.py reportOptionalMemberAccess 1 "findall" is not a known attribute of "None"
pyright dm/tools/check_thread_cuts.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/check_thread_cuts.py reportMissingImports 1 Import "unicorn" could not be resolved
pyright dm/tools/check_thread_cuts.py reportMissingImports 1 Import "unicorn.arm64_const" could not be resolved
pyright dm/tools/check_thread_cuts.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/check_thread_cuts.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/docs_symptoms.py reportOptionalMemberAccess 1 "group" is not a known attribute of "None"
pyright dm/tools/gpuprof.py reportArgumentType 1 Argument of type "Unknown | None" cannot be assigned to parameter "value" of type "int" in function "__setitem__" Type "Unknown | None" is not assignable to type "int" "None" is not assignable to "int"
pyright dm/tools/gpuprof.py reportOptionalMemberAccess 1 "split" is not a known attribute of "None"
pyright dm/tools/make_ext_font.py reportOptionalSubscript 1 Object of type "None" is not subscriptable
pyright dm/tools/nro_collect.py reportMissingImports 1 Import "backports" could not be resolved
pyright dm/tools/nro_collect.py reportOptionalIterable 1 Object of type "None" cannot be used as iterable value
pyright dm/tools/nso_fnmap.py reportMissingImports 1 Import "numpy" could not be resolved
pyright dm/tools/nvn_slots_got.py reportOperatorIssue 1 Operator "+=" not supported for types "int | None" and "int" Operator "+" not supported for types "None" and "int"
pyright dm/tools/nvn_slots_got.py reportOptionalOperand 2 Operator "+" not supported for "None"
pyright dm/tools/patch_nso.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/patch_nso.py reportArgumentType 1 Argument of type "set[Unknown] | list[Unknown]" cannot be assigned to parameter "a" of type "SupportsLenAndGetItem[SupportsRichComparisonT@bisect_right]" in function "bisect_right" Type "set[Unknown] | list[Unknown]" is not assignable to type "SupportsLenAndGetItem[Unknown]" "set[Unknown]" is incompatible with protocol "SupportsLenAndGetItem[Unknown]" "__getitem__" is not present
pyright dm/tools/patch_nso.py reportArgumentType 1 Argument of type "tuple[Any, ...] | None" cannot be assigned to parameter "iterable" of type "Iterable[SupportsRichComparisonT@max]" in function "max" Type "tuple[Any, ...] | None" is not assignable to type "Iterable[Any]" "None" is incompatible with protocol "Iterable[Any]" "__iter__" is not present
pyright dm/tools/patch_nso.py reportArgumentType 4 Argument of type "str | None" cannot be assigned to parameter "string" of type "str" in function "fullmatch" Type "str | None" is not assignable to type "str" "None" is not assignable to "str"
pyright dm/tools/patch_nso.py reportCallIssue 4 No overloads for "fullmatch" match the provided arguments
pyright dm/tools/patch_nso.py reportIndexIssue 3 "__getitem__" method not defined on type "set[Unknown]"
pyright dm/tools/patch_nso.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/patch_nso.py reportOptionalMemberAccess 1 "group" is not a known attribute of "None"
pyright dm/tools/patch_nso.py reportOptionalMemberAccess 1 "groups" is not a known attribute of "None"
pyright dm/tools/patch_nso.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/patch_nso.py reportOptionalMemberAccess 1 "op_str" is not a known attribute of "None"
pyright dm/tools/patch_nso.py reportOptionalMemberAccess 1 "replace" is not a known attribute of "None"
pyright dm/tools/patch_nso.py reportOptionalOperand 2 Operator "+" not supported for "None"
pyright dm/tools/patch_nso.py reportOptionalOperand 2 Operator "<=" not supported for "None"
pyright dm/tools/patch_nso.py reportOptionalSubscript 3 Object of type "None" is not subscriptable
pyright dm/tools/patch_nso.py reportWildcardImportFromLibrary 1 Wildcard import from a library not allowed
pyright dm/tools/sdis.py reportWildcardImportFromLibrary 1 Wildcard import from a library not allowed
pyright dm/tools/tests/test_astral_fps.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_astral_fps.py reportMissingImports 2 Import "unicorn" could not be resolved
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "UC_ARCH_ARM64" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "UC_ARM64_REG_LR" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "UC_ARM64_REG_S1" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "UC_ARM64_REG_S10" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "UC_ARM64_REG_S8" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "UC_ARM64_REG_SP" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "UC_ARM64_REG_X19" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "UC_ARM64_REG_X24" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "UC_MODE_ARM" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "Uc" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 2 "UC_ARM64_REG_NZCV" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 2 "UC_ARM64_REG_W21" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 2 "UC_ARM64_REG_X0" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 2 "UC_ARM64_REG_X1" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 2 "UC_ARM64_REG_X16" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 2 "UC_ARM64_REG_X17" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 2 "UC_ARM64_REG_X2" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 2 "UC_HOOK_CODE" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 3 "UC_ARM64_REG_S0" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 4 "UC_ARM64_REG_X8" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 6 "UC_ARM64_REG_PC" is not a known attribute of "None"
pyright dm/tools/tests/test_astral_fps.py reportOptionalMemberAccess 6 "UC_ARM64_REG_W8" is not a known attribute of "None"
pyright dm/tools/tests/test_bench.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_bench.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_bench.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_gpuprof.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_gpuprof.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_gpuprof.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_image_atomic_bounds.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_image_atomic_bounds.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_image_atomic_bounds.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_image_atomic_bounds.py reportOptionalSubscript 1 Object of type "None" is not subscriptable
pyright dm/tools/tests/test_launcher_settings.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_launcher_settings.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_launcher_settings.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_nro_graphics.py reportArgumentType 2 Argument of type "set[str]" cannot be assigned to parameter "nro_imports" of type "frozenset[Unknown]" in function "shader_sources" "set[str]" is not assignable to "frozenset[Unknown]"
pyright dm/tools/tests/test_nro_pack.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_nro_pack.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_nro_pack.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_nso_match.py reportIndexIssue 3 "__setitem__" method not defined on type "str"
pyright dm/tools/tests/test_nsp_alias.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_nsp_alias.py reportAttributeAccessIssue 1 Cannot assign to attribute "nacp" for class "ModuleType" Attribute "nacp" is unknown
pyright dm/tools/tests/test_nsp_alias.py reportAttributeAccessIssue 1 Cannot assign to attribute "pg" for class "ModuleType" Attribute "pg" is unknown
pyright dm/tools/tests/test_nsp_alias.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_alias.py reportOptionalMemberAccess 1 "id" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_alias.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_chain.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_nsp_chain.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_chain.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_dotnet.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_nsp_dotnet.py reportArgumentType 1 Argument of type "OSError | TimeoutExpired | CalledProcessError | CompletedProcess[str]" cannot be assigned to parameter "create" of type "bool" in function "object" Type "OSError | TimeoutExpired | CalledProcessError | CompletedProcess[str]" is not assignable to type "bool" "CalledProcessError" is not assignable to "bool"
pyright dm/tools/tests/test_nsp_dotnet.py reportArgumentType 1 Argument of type "OSError | TimeoutExpired | CalledProcessError | CompletedProcess[str]" cannot be assigned to parameter "new_callable" of type "None" in function "object" Type "OSError | TimeoutExpired | CalledProcessError | CompletedProcess[str]" is not assignable to type "None" "CalledProcessError" is not assignable to "None"
pyright dm/tools/tests/test_nsp_dotnet.py reportArgumentType 1 Argument of type "OSError | TimeoutExpired | CalledProcessError | CompletedProcess[str]" cannot be assigned to parameter "unsafe" of type "bool" in function "object" Type "OSError | TimeoutExpired | CalledProcessError | CompletedProcess[str]" is not assignable to type "bool" "CalledProcessError" is not assignable to "bool"
pyright dm/tools/tests/test_nsp_dotnet.py reportCallIssue 1 No overloads for "object" match the provided arguments
pyright dm/tools/tests/test_nsp_dotnet.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_dotnet.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_incremental.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_nsp_incremental.py reportArgumentType 1 Argument of type "int | str | tuple[str, str]" cannot be assigned to parameter "ver" of type "str" in function "derive" Type "int | str | tuple[str, str]" is not assignable to type "str" "int" is not assignable to "str"
pyright dm/tools/tests/test_nsp_incremental.py reportAttributeAccessIssue 1 Cannot assign to attribute "nacp" for class "ModuleType" Attribute "nacp" is unknown
pyright dm/tools/tests/test_nsp_incremental.py reportAttributeAccessIssue 1 Cannot assign to attribute "pg" for class "ModuleType" Attribute "pg" is unknown
pyright dm/tools/tests/test_nsp_incremental.py reportOptionalCall 2 Object of type "None" cannot be called
pyright dm/tools/tests/test_nsp_incremental.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_incremental.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_mac.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_nsp_mac.py reportOperatorIssue 1 Operator "in" not supported for types "Literal['IOSOUT']" and "Unknown | None" Operator "in" not supported for types "Literal['IOSOUT']" and "None"
pyright dm/tools/tests/test_nsp_mac.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_mac.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_mac.py reportOptionalSubscript 1 Object of type "None" is not subscriptable
pyright dm/tools/tests/test_nsp_mac_app.py reportArgumentType 1 Argument of type "Unknown | None" cannot be assigned to parameter "obj" of type "Sized" in function "len" Type "Unknown | None" is not assignable to type "Sized" "None" is incompatible with protocol "Sized" "__len__" is not present
pyright dm/tools/tests/test_nsp_mac_app.py reportArgumentType 1 Argument of type "str | None" cannot be assigned to parameter "src" of type "StrPath" in function "copy2" Type "str | None" is not assignable to type "StrPath" Type "None" is not assignable to type "StrPath" "None" is not assignable to "str" "None" is incompatible with protocol "PathLike[str]" "__fspath__" is not present
pyright dm/tools/tests/test_nsp_mac_app.py reportArgumentType 2 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_nsp_mac_app.py reportCallIssue 1 No overloads for "copy2" match the provided arguments
pyright dm/tools/tests/test_nsp_mac_app.py reportOptionalMemberAccess 2 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_mac_app.py reportOptionalMemberAccess 2 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_mac_app.py reportOptionalSubscript 4 Object of type "None" is not subscriptable
pyright dm/tools/tests/test_nsp_nsz.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_nsp_nsz.py reportArgumentType 2 Argument of type "Unknown | None" cannot be assigned to parameter "args" of type "StrPath" in function "__init__" Type "Unknown | None" is not assignable to type "StrPath" Type "None" is not assignable to type "StrPath" "None" is not assignable to "str" "None" is incompatible with protocol "PathLike[str]" "__fspath__" is not present
pyright dm/tools/tests/test_nsp_nsz.py reportArgumentType 2 Argument of type "Unknown | None" cannot be assigned to parameter "args" of type "StrPath" in function "__new__" Type "Unknown | None" is not assignable to type "StrPath" Type "None" is not assignable to type "StrPath" "None" is not assignable to "str" "None" is incompatible with protocol "PathLike[str]" "__fspath__" is not present
pyright dm/tools/tests/test_nsp_nsz.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_nsz.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_progress.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_nsp_progress.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_nsp_progress.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_patches.py reportArgumentType 1 Argument of type "Literal['CUTS', 'OBS', 'FRAME_STEP']" cannot be assigned to parameter "key" of type "SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" in function "__getitem__" Type "Literal['CUTS', 'OBS', 'FRAME_STEP']" is not assignable to type "SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" Type "Literal['CUTS']" is not assignable to type "SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" "Literal['CUTS']" is incompatible with protocol "SupportsIndex" "__index__" is not present "Literal['CUTS']" is not assignable to "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]"
pyright dm/tools/tests/test_patches.py reportArgumentType 1 Argument of type "Literal['CUTS']" cannot be assigned to parameter "key" of type "SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" in function "__getitem__" Type "Literal['CUTS']" is not assignable to type "SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" "Literal['CUTS']" is incompatible with protocol "SupportsIndex" "__index__" is not present "Literal['CUTS']" is not assignable to "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]"
pyright dm/tools/tests/test_patches.py reportArgumentType 1 Argument of type "tuple[Literal['CUTS', 'OBS', 'FRAME_STEP'], LiteralString]" cannot be assigned to parameter "key" of type "SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" in function "__getitem__" Type "tuple[Literal['CUTS', 'OBS', 'FRAME_STEP'], LiteralString]" is not assignable to type "SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" "tuple[Literal['CUTS', 'OBS', 'FRAME_STEP'], LiteralString]" is incompatible with protocol "SupportsIndex" "__index__" is not present "tuple[Literal['CUTS', 'OBS', 'FRAME_STEP'], LiteralString]" is not assignable to "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]"
pyright dm/tools/tests/test_patches.py reportArgumentType 3 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_patches.py reportArgumentType 3 Argument of type "str" cannot be assigned to parameter "key" of type "SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" in function "__getitem__" Type "str" is not assignable to type "SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" "str" is incompatible with protocol "SupportsIndex" "__index__" is not present "str" is not assignable to "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]"
pyright dm/tools/tests/test_patches.py reportArgumentType 5 Argument of type "Literal['MATCH_CUTS']" cannot be assigned to parameter "key" of type "SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" in function "__getitem__" Type "Literal['MATCH_CUTS']" is not assignable to type "SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]" "Literal['MATCH_CUTS']" is incompatible with protocol "SupportsIndex" "__index__" is not present "Literal['MATCH_CUTS']" is not assignable to "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]"
pyright dm/tools/tests/test_patches.py reportIndexIssue 8 Object of type "Module("glob")" is not subscriptable
pyright dm/tools/tests/test_patches.py reportIndexIssue 8 Object of type "Module("importlib")" is not subscriptable
pyright dm/tools/tests/test_patches.py reportIndexIssue 8 Object of type "Module("os")" is not subscriptable
pyright dm/tools/tests/test_patches.py reportOptionalMemberAccess 3 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_patches.py reportOptionalMemberAccess 3 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_pso_offline.py reportArgumentType 1 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_pso_offline.py reportOptionalMemberAccess 1 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_pso_offline.py reportOptionalMemberAccess 1 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_shader_regressions.py reportArgumentType 2 Argument of type "ModuleSpec | None" cannot be assigned to parameter "spec" of type "ModuleSpec" in function "module_from_spec" Type "ModuleSpec | None" is not assignable to type "ModuleSpec" "None" is not assignable to "ModuleSpec"
pyright dm/tools/tests/test_shader_regressions.py reportOptionalMemberAccess 2 "exec_module" is not a known attribute of "None"
pyright dm/tools/tests/test_shader_regressions.py reportOptionalMemberAccess 2 "loader" is not a known attribute of "None"
pyright dm/tools/tests/test_spin_waits.py reportArgumentType 1 Argument of type "list[FunctionDef | ClassDef | Assign]" cannot be assigned to parameter "body" of type "list[stmt]" in function "__init__" "list[FunctionDef | ClassDef | Assign]" is not assignable to "list[stmt]" Type parameter "_T@list" is invariant, but "FunctionDef | ClassDef | Assign" is not the same as "stmt" Consider switching from "list" to "Sequence" which is covariant
pyright dm/tools/tests/test_tail_call.py reportCallIssue 3 Object of type "ModuleType" is not callable Attribute "__call__" is unknown
pyright scripts/bench.py reportArgumentType 1 Argument of type "Unknown | None" cannot be assigned to parameter "number" of type "_SupportsRound2[_T@round]" in function "round" Type "Unknown | None" is not assignable to type "_SupportsRound2[_T@round]" "None" is incompatible with protocol "_SupportsRound2[_T@round]" "__round__" is not present
pyright scripts/bench.py reportArgumentType 1 Argument of type "float" cannot be assigned to parameter "value" of type "int" in function "__setitem__" "float" is not assignable to "int"
pyright scripts/bench.py reportCallIssue 1 No overloads for "round" match the provided arguments
pyright scripts/bench.py reportOptionalMemberAccess 1 "group" is not a known attribute of "None"
pyright scripts/bench.py reportOptionalOperand 1 Operator "/" not supported for "None"
pyright scripts/build_ipa_from_nsp.py reportArgumentType 1 Argument of type "str | ((n: Unknown) -> (Unknown | None))" cannot be assigned to parameter "func" of type "(_T1@__new__) -> _S@map" in function "__new__" Type "str | ((n: Unknown) -> (Unknown | None))" is not assignable to type "(Unknown) -> _S@map" Type "str" is not assignable to type "(Unknown) -> _S@map"
pyright scripts/build_ipa_from_nsp.py reportArgumentType 1 Argument of type "str | ((n: Unknown) -> (Unknown | None))" cannot be assigned to parameter "func" of type "(_T1@__new__) -> _S@map" in function "__new__" Type "str | ((n: Unknown) -> (Unknown | None))" is not assignable to type "(_T1@__new__) -> _S@map" Type "str" is not assignable to type "(_T1@__new__) -> _S@map"
pyright scripts/build_ipa_from_nsp.py reportCallIssue 1 Argument expression after ** must be a mapping with a "str" key type
pyright scripts/build_ipa_from_nsp.py reportCallIssue 1 No overloads for "__init__" match the provided arguments Argument types: (Unknown, str, str, str, str, **dict[str | ((n: Unknown) -> (Unknown | None)), str])
pyright scripts/build_ipa_from_nsp.py reportOptionalIterable 1 Object of type "None" cannot be used as iterable value
pyright scripts/build_ipa_from_nsp.py reportOptionalMemberAccess 1 "close" is not a known attribute of "None"
pyright scripts/build_ipa_from_nsp.py reportOptionalOperand 1 Operator "-" not supported for "None"
ruff dm/gfx/tools/dmsh_build.py F841 1 Local variable `sb_cb` is assigned to but never used
ruff dm/gfx/tools/pso_offline.py F401 1 `shutil` imported but unused
ruff dm/gfx/tools/shader_split.py B006 1 Do not use mutable data structures for argument defaults
ruff dm/gfx/tools/shader_split.py B023 1 Function definition does not bind loop variable `c`
ruff dm/gfx/tools/shader_split.py B023 1 Function definition does not bind loop variable `order`
ruff dm/gfx/tools/shader_split.py B023 1 Function definition does not bind loop variable `tab`
ruff dm/gfx/tools/shader_split.py B023 3 Function definition does not bind loop variable `r`
ruff dm/gfx/tools/shader_split.py B023 3 Function definition does not bind loop variable `sec`
ruff dm/gfx/tools/shader_split.py B023 3 Function definition does not bind loop variable `secs`
ruff dm/gfx/tools/shader_split.py F841 1 Local variable `pre` is assigned to but never used
ruff dm/gfx/tools/unityfs.py B023 2 Function definition does not bind loop variable `buf`
ruff dm/tools/patch_nso.py B023 1 Function definition does not bind loop variable `at`
ruff dm/tools/patch_nso.py B023 2 Function definition does not bind loop variable `n`
ruff dm/tools/patch_nso.py PIE808 1 Unnecessary `start` argument in `range`
ruff dm/tools/sdis.py F841 1 Local variable `lo` is assigned to but never used
ruff dm/tools/tests/test_gpuprof.py F401 1 `os` imported but unused
ruff dm/tools/tests/test_gpuprof.py F401 1 `sys` imported but unused
ruff dm/tools/tests/test_image_atomic_bounds.py B023 1 Function definition does not bind loop variable `dependencies`
ruff dm/tools/tests/test_nsp_incremental.py B023 1 Function definition does not bind loop variable `build`
ruff dm/tools/tests/test_nsp_incremental.py B023 1 Function definition does not bind loop variable `own`
ruff dm/tools/tests/test_nsp_incremental.py B023 1 Function definition does not bind loop variable `roots`
ruff dm/tools/tests/test_nsp_incremental.py B023 1 Function definition does not bind loop variable `sh`
ruff dm/tools/tests/test_nsp_nsz.py F401 1 `sys` imported but unused
ruff dm/tools/tests/test_spin_waits.py F401 1 `sys` imported but unused
ruff scripts/bench.py B023 1 Function definition does not bind loop variable `ok`
ruff scripts/build_ipa_from_nsp.py B020 1 Loop control variable `note` overrides iterable it iterates
ruff scripts/build_ipa_from_nsp.py F401 1 `zlib` imported but unused
Can't render this file because it contains an unexpected character in line 2 and column 73.
+119
View File
@@ -0,0 +1,119 @@
"""The lint baseline may grow only for a checker whose pinned version increased.
Uses temporary Git trees; no linters, game files or keys needed.
"""
import collections
import contextlib
import io
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import lint_python as lint
REQS = 'ruff==0.17.2\npyright==1.1.414\nlz4==4.4.5\n'
RUFF = ('ruff', 'scripts/example.py', 'F821', 'Undefined name `missing`')
PYRIGHT = ('pyright', 'scripts/example.py', 'reportUndefinedVariable', '"missing" is not defined')
class BaselineGuard(unittest.TestCase):
def setUp(self):
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.root = Path(tmp.name).resolve()
self.baseline = self.root / 'dm/tools/lint_python_baseline.tsv'
self.baseline.parent.mkdir(parents=True)
self.reqs = self.root / lint.REQS
self.reqs.parent.mkdir(parents=True)
self.reqs.write_text(REQS)
patch = mock.patch.multiple(lint, REPO=self.root, BASELINE=self.baseline)
patch.start(); self.addCleanup(patch.stop)
self.base: collections.Counter[tuple[str, str, str, str]] = collections.Counter({RUFF: 1, PYRIGHT: 1})
lint.write(self.base)
self.git('init', '-q')
self.git('add', '.')
self.rev = self.git('write-tree')
def git(self, *args):
return subprocess.run(['git', *args], cwd=self.root, capture_output=True, text=True, check=True).stdout.strip()
def compare(self, base):
with contextlib.redirect_stdout(io.StringIO()) as out:
rc = lint.against(self.rev, base)
return rc, out.getvalue()
def grown(self, tool):
base = self.base.copy()
base[RUFF if tool == 'ruff' else PYRIGHT] += 1
return base
def test_unchanged_and_shrunk_baselines_pass(self):
for base in (self.base, collections.Counter({RUFF: 1}), collections.Counter()):
with self.subTest(base=base): self.assertEqual(self.compare(base)[0], 0)
def test_new_entry_and_increased_count_fail_without_upgrade(self):
added = self.base.copy()
added[('ruff', 'scripts/another.py', 'F821', 'Undefined name `missing`')] = 1
for base in (added, self.grown('ruff'), self.grown('pyright')):
with self.subTest(base=base): self.assertEqual(self.compare(base)[0], 1)
def test_comments_whitespace_and_dependencies_do_not_allow_growth(self):
edits = (REQS + '# comment only\n', REQS.replace('ruff==', 'ruff == ') + '\n',
REQS.replace('4.4.5', '4.4.6'), REQS + 'capstone==5.0.9\n')
for reqs in edits:
self.reqs.write_text(reqs)
with self.subTest(reqs=reqs): self.assertEqual(self.compare(self.base)[0], 0)
for tool in ('ruff', 'pyright'):
with self.subTest(reqs=reqs, tool=tool): self.assertEqual(self.compare(self.grown(tool))[0], 1)
def test_downgrades_do_not_allow_growth(self):
for old, new in (('0.17.2', '0.17.1'), ('1.1.414', '1.1.413')):
self.reqs.write_text(REQS.replace(old, new))
with self.subTest(version=new): self.assertEqual(self.compare(self.base)[0], 0)
for tool in ('ruff', 'pyright'):
with self.subTest(version=new, tool=tool): self.assertEqual(self.compare(self.grown(tool))[0], 1)
def test_upgrade_allows_only_that_checkers_growth(self):
for upgraded, old, new, other in (('ruff', '0.17.2', '0.18.0', 'pyright'), ('pyright', '1.1.414', '1.1.415', 'ruff')):
self.reqs.write_text(REQS.replace(old, new))
with self.subTest(tool=upgraded):
self.assertEqual(self.compare(self.grown(upgraded))[0], 0)
self.assertEqual(self.compare(self.grown(other))[0], 1)
both = self.grown(upgraded); both.update({RUFF if other == 'ruff' else PYRIGHT: 1})
self.assertEqual(self.compare(both)[0], 1)
def test_version_comparison_is_numeric(self):
self.reqs.write_text(REQS.replace('0.17.2', '0.9.0'))
self.assertEqual(self.compare(self.grown('ruff'))[0], 1)
self.reqs.write_text(REQS.replace('0.17.2', '0.100.0'))
self.assertEqual(self.compare(self.grown('ruff'))[0], 0)
def test_both_upgrades_allow_both_checkers_growth(self):
self.reqs.write_text(REQS.replace('0.17.2', '0.18.0').replace('1.1.414', '1.1.415'))
self.assertEqual(self.compare(collections.Counter({RUFF: 2, PYRIGHT: 2}))[0], 0)
def test_missing_or_invalid_pins_fail_closed(self):
edits = (REQS.replace('ruff==0.17.2\n', ''), REQS.replace('ruff==0.17.2', 'ruff>=0.18.0'),
REQS.replace('0.17.2', 'invalid'), REQS + 'ruff==0.18.0\n')
for reqs in edits:
self.reqs.write_text(reqs)
with self.subTest(reqs=reqs): self.assertEqual(self.compare(self.grown('ruff'))[0], 1)
def test_missing_base_requirements_fail_closed(self):
self.git('rm', '--cached', '-q', lint.REQS)
self.rev = self.git('write-tree')
self.reqs.write_text(REQS)
self.assertEqual(self.compare(self.grown('ruff'))[0], 1)
def test_first_baseline_needs_no_previous_requirements(self):
self.git('rm', '--cached', '-q', lint.rel(self.baseline), lint.REQS)
self.rev = self.git('write-tree')
self.reqs.write_text(REQS)
self.assertEqual(self.compare(self.base)[0], 0)
if __name__ == '__main__':
unittest.main()
+20
View File
@@ -71,6 +71,26 @@ Generated projects and Xcode build products stay in that folder; the host profil
header is generated under `dm/host/gen/` as usual. CI uploads failure logs.
GPU regression tests still run locally with `make -C dm/gfx/tests GAME=<id> run`.
`.github/workflows/lint-python.yml` lints `scripts/` and the Python tools under
`dm/` with ruff (rules for bugs, never reformatting) and type-checks them with
pyright in basic mode (`pyproject.toml`). It is a ratchet: today's findings are
in `dm/tools/lint_python_baseline.tsv`, so it fails only on a new finding, or on
a fixed one still listed there. Astral's ty, a faster type checker, can replace
pyright once it is stable. With the pinned tools in a new venv:
```sh
python3 -m venv /path/to/lint-venv && /path/to/lint-venv/bin/pip install -r .github/requirements/lint.txt
/path/to/lint-venv/bin/python3 dm/tools/lint_python.py # the check CI runs
/path/to/lint-venv/bin/python3 dm/tools/lint_python.py --update # after fixing findings: shrink the baseline
```
The baseline never gains entries: fix a new finding, or silence it on its line
(`# noqa: <rule>`, `# pyright: ignore[<rule>]`) with a reason. Only a raised tool
version in `.github/requirements/lint.txt` rewrites it (`--rebaseline`). On pull
requests, only the checker whose pinned release version increased may gain
baseline entries or counts. Comments, dependency updates and checker downgrades
do not allow growth.
## The packing script in detail
`scripts/build_ipa_from_nsp.py` (`-h` lists every option):
+19
View File
@@ -0,0 +1,19 @@
# Configuration of the Python lint and type checks only (.github/workflows/lint-python.yml, dm/tools/lint_python.py); the repository is not a Python package.
# Scope: scripts/ and the tools under dm/. extraPaths / extra-paths are the folders the tools put on sys.path to import each other.
[tool.pyright]
include = ["scripts", "dm/tools", "dm/gfx/tools", "dm/games/game.py", "dm/games/profile_guess.py", "dm/games/settings.py", "dm/ios/tools", "dm/mac"]
extraPaths = ["dm/tools", "dm/gfx/tools", "dm/games", "dm/ios/tools", "dm/mac"]
typeCheckingMode = "basic"
pythonVersion = "3.14"
pythonPlatform = "Darwin"
[tool.ruff]
target-version = "py310"
include = ["scripts/**/*.py", "dm/tools/**/*.py", "dm/gfx/tools/**/*.py", "dm/games/*.py", "dm/ios/tools/**/*.py", "dm/mac/**/*.py"]
# Rules that find bugs (pyflakes, syntax, bugbear, pylint's errors and warnings, a few of ruff's own), not style: the code's dense layout (several statements on a
# line, star imports of the rewriter's tables, short names) is the house style, so those rules are off, and `ruff format` is not used.
[tool.ruff.lint]
select = ["F", "E7", "E9", "B", "PLE", "PLW", "PIE", "ISC001", "T10", "RUF006", "RUF008", "RUF009", "RUF013", "RUF016", "RUF017", "RUF018", "RUF024", "RUF026"]
ignore = ["F403", "F405", "E701", "E702", "E731", "E741", "B007", "B905", "PLW0603", "PLW1510", "PLW2901"]