Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install ruff
python -m pip install ruff ty

- name: Lint with Ruff on a basic set of rules
run: |
Expand All @@ -42,3 +42,7 @@ jobs:
- name: Lint with Ruff on an extended ruleset but always return success
run: |
ruff check libdestruct --output-format=github --exit-zero

- name: Type check with ty
run: |
ty check --output-format=github
2 changes: 1 addition & 1 deletion libdestruct/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#

try: # pragma: no cover
from rich.traceback import install
from rich.traceback import install # ty: ignore[unresolved-import]

install()
except ImportError: # pragma: no cover
Expand Down
4 changes: 3 additions & 1 deletion libdestruct/backing/fake_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@

from __future__ import annotations

from typing import Literal

from libdestruct.backing.resolver import Resolver


class FakeResolver(Resolver):
"""A class that can resolve elements in a simulated memory storage."""

def __init__(self: FakeResolver, memory: dict | None = None, address: int | None = 0, endianness: str = "little") -> None:
def __init__(self: FakeResolver, memory: dict | None = None, address: int | None = 0, endianness: Literal["little", "big"] = "little") -> None:

Check failure on line 17 in libdestruct/backing/fake_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ruff (E501)

libdestruct/backing/fake_resolver.py:17:121: E501 Line too long (147 > 120)
"""Initializes a basic fake resolver."""
self.memory = memory if memory is not None else {}
self.address = address
self.parent = None

Check warning on line 21 in libdestruct/backing/fake_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ty (invalid-assignment)

libdestruct/backing/fake_resolver.py:21:9: invalid-assignment: Object of type `None` is not assignable to attribute `parent` of type `FakeResolver` info: rule `invalid-assignment` was selected in the configuration file
self.offset = None
self.endianness = endianness

Expand All @@ -25,9 +27,9 @@
if self.address is not None:
return self.address

return self.parent.resolve_address() + self.offset

Check warning on line 30 in libdestruct/backing/fake_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ty (unsupported-operator)

libdestruct/backing/fake_resolver.py:30:16: unsupported-operator: Operator `+` is not supported between objects of type `int` and `Unknown | None` libdestruct/backing/fake_resolver.py:30:16: Has type `int` libdestruct/backing/fake_resolver.py:30:48: Has type `Unknown | None` info: rule `unsupported-operator` was selected in the configuration file

def relative_from_own(self: FakeResolver, address_offset: int, _: int) -> FakeResolver:

Check warning on line 32 in libdestruct/backing/fake_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ty (invalid-method-override)

libdestruct/backing/fake_resolver.py:32:9: invalid-method-override: Invalid override of method `relative_from_own`: Definition is incompatible with `Resolver.relative_from_own` libdestruct/backing/resolver.py:24:9: `Resolver.relative_from_own` defined here info: This violates the Liskov Substitution Principle info: rule `invalid-method-override` was selected in the configuration file
"""Creates a resolver that references a parent, such that a change in the parent is propagated on the child."""
new_resolver = FakeResolver(self.memory, None, self.endianness)
new_resolver.parent = self
Expand All @@ -38,7 +40,7 @@
"""Creates a resolver that has an absolute reference to an object, from the parent's view."""
return FakeResolver(self.memory, address, self.endianness)

def resolve(self: FakeResolver, size: int, _: int) -> bytes:

Check warning on line 43 in libdestruct/backing/fake_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ty (invalid-method-override)

libdestruct/backing/fake_resolver.py:43:9: invalid-method-override: Invalid override of method `resolve`: Definition is incompatible with `Resolver.resolve` libdestruct/backing/resolver.py:36:9: `Resolver.resolve` defined here info: This violates the Liskov Substitution Principle info: rule `invalid-method-override` was selected in the configuration file
"""Resolves itself, providing the bytes it references for the specified size and index."""
address = self.resolve_address()
# We store data in the dictionary as 4K pages
Expand All @@ -57,7 +59,7 @@

return result

def modify(self: FakeResolver, size: int, _: int, value: bytes) -> None:

Check warning on line 62 in libdestruct/backing/fake_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ty (invalid-method-override)

libdestruct/backing/fake_resolver.py:62:9: invalid-method-override: Invalid override of method `modify`: Definition is incompatible with `Resolver.modify` libdestruct/backing/resolver.py:40:9: `Resolver.modify` defined here info: This violates the Liskov Substitution Principle info: rule `invalid-method-override` was selected in the configuration file
"""Modifies itself in memory."""
address = self.resolve_address()
# We store data in the dictionary as 4K pages
Expand Down
4 changes: 2 additions & 2 deletions libdestruct/backing/memory_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Literal

from libdestruct.backing.resolver import Resolver

Expand All @@ -17,11 +17,11 @@
class MemoryResolver(Resolver):
"""A class that can resolve itself to a value in a referenced memory storage."""

def __init__(self: MemoryResolver, memory: MutableSequence, address: int | None, endianness: str = "little") -> None:
def __init__(self: MemoryResolver, memory: MutableSequence, address: int | None, endianness: Literal["little", "big"] = "little") -> None:

Check failure on line 20 in libdestruct/backing/memory_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ruff (E501)

libdestruct/backing/memory_resolver.py:20:121: E501 Line too long (142 > 120)
"""Initializes a basic memory resolver."""
self.memory = memory
self.address = address
self.parent = None

Check warning on line 24 in libdestruct/backing/memory_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ty (invalid-assignment)

libdestruct/backing/memory_resolver.py:24:9: invalid-assignment: Object of type `None` is not assignable to attribute `parent` of type `MemoryResolver` info: rule `invalid-assignment` was selected in the configuration file
self.offset = None
self.endianness = endianness

Expand All @@ -30,9 +30,9 @@
if self.address is not None:
return self.address

return self.parent.resolve_address() + self.offset

Check warning on line 33 in libdestruct/backing/memory_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ty (unsupported-operator)

libdestruct/backing/memory_resolver.py:33:16: unsupported-operator: Operator `+` is not supported between objects of type `int` and `Unknown | None` libdestruct/backing/memory_resolver.py:33:16: Has type `int` libdestruct/backing/memory_resolver.py:33:48: Has type `Unknown | None` info: rule `unsupported-operator` was selected in the configuration file

def relative_from_own(self: MemoryResolver, address_offset: int, _: int) -> MemoryResolver:

Check warning on line 35 in libdestruct/backing/memory_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ty (invalid-method-override)

libdestruct/backing/memory_resolver.py:35:9: invalid-method-override: Invalid override of method `relative_from_own`: Definition is incompatible with `Resolver.relative_from_own` libdestruct/backing/resolver.py:24:9: `Resolver.relative_from_own` defined here info: This violates the Liskov Substitution Principle info: rule `invalid-method-override` was selected in the configuration file
"""Creates a resolver that references a parent, such that a change in the parent is propagated on the child."""
new_resolver = MemoryResolver(self.memory, None, self.endianness)
new_resolver.parent = self
Expand All @@ -43,12 +43,12 @@
"""Creates a resolver that has an absolute reference to an object, from the parent's view."""
return MemoryResolver(self.memory, address, self.endianness)

def resolve(self: MemoryResolver, size: int, _: int) -> bytes:

Check warning on line 46 in libdestruct/backing/memory_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ty (invalid-method-override)

libdestruct/backing/memory_resolver.py:46:9: invalid-method-override: Invalid override of method `resolve`: Definition is incompatible with `Resolver.resolve` libdestruct/backing/resolver.py:36:9: `Resolver.resolve` defined here info: This violates the Liskov Substitution Principle info: rule `invalid-method-override` was selected in the configuration file
"""Resolves itself, providing the bytes it references for the specified size and index."""
address = self.resolve_address()
return bytes(self.memory[address : address + size])

def modify(self: MemoryResolver, size: int, _: int, value: bytes) -> None:

Check warning on line 51 in libdestruct/backing/memory_resolver.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ty (invalid-method-override)

libdestruct/backing/memory_resolver.py:51:9: invalid-method-override: Invalid override of method `modify`: Definition is incompatible with `Resolver.modify` libdestruct/backing/resolver.py:40:9: `Resolver.modify` defined here info: This violates the Liskov Substitution Principle info: rule `invalid-method-override` was selected in the configuration file
"""Modifies itself in memory."""
address = self.resolve_address()
self.memory[address : address + size] = value
3 changes: 2 additions & 1 deletion libdestruct/backing/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Literal

from typing_extensions import Self

Expand All @@ -16,7 +17,7 @@ class Resolver(ABC):

parent: Self

endianness: str = "little"
endianness: Literal["little", "big"] = "little"
"""The endianness of the data this resolver accesses."""

@abstractmethod
Expand Down
1 change: 1 addition & 0 deletions libdestruct/c/c_integer_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
def to_bytes(self: _c_integer) -> bytes:
"""Return the serialized representation of the object."""
if self._frozen:
assert isinstance(self._frozen_value, int)

Check failure on line 31 in libdestruct/c/c_integer_types.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ruff (S101)

libdestruct/c/c_integer_types.py:31:13: S101 Use of `assert` detected
return self._frozen_value.to_bytes(self.size, self.endianness, signed=self.signed)

return self.resolver.resolve(self.size, 0)
Expand Down
4 changes: 3 additions & 1 deletion libdestruct/c/c_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from __future__ import annotations

from collections.abc import Iterator

Check failure on line 9 in libdestruct/c/c_str.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ruff (TC003)

libdestruct/c/c_str.py:9:29: TC003 Move standard library import `collections.abc.Iterator` into a type-checking block help: Move into type-checking block

from libdestruct.common.array.array import array


Expand Down Expand Up @@ -54,7 +56,7 @@
"""Set the character at the given index to the given value."""
self._set(value, index)

def __iter__(self: c_str) -> iter:
def __iter__(self: c_str) -> Iterator:
"""Return an iterator over the string."""
for i in range(self.count()):
yield self.get(i)
3 changes: 2 additions & 1 deletion libdestruct/common/array/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from __future__ import annotations

from abc import abstractmethod
from collections.abc import Iterator

Check failure on line 10 in libdestruct/common/array/array.py

View workflow job for this annotation

GitHub Actions / lint (3.12)

ruff (TC003)

libdestruct/common/array/array.py:10:29: TC003 Move standard library import `collections.abc.Iterator` into a type-checking block help: Move into type-checking block
from types import GenericAlias

from libdestruct.common.obj import obj
Expand Down Expand Up @@ -42,7 +43,7 @@
self.set(index, value)

@abstractmethod
def __iter__(self: array) -> iter:
def __iter__(self: array) -> Iterator:
"""Return an iterator over the array."""

def __contains__(self: array, value: object) -> bool:
Expand Down
4 changes: 2 additions & 2 deletions libdestruct/common/enum/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def __class_getitem__(cls, params: tuple) -> GenericAlias:
python_enum: type[Enum]
"""The backing Python enum."""

_backing_type: type[obj]
"""The backing type."""
_backing_type: obj
"""The inflated backing instance."""

lenient: bool
"""Whether the conversion is lenient or not."""
Expand Down
2 changes: 1 addition & 1 deletion libdestruct/common/enum/int_enum_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def __init__(
case _:
raise ValueError("The size of the field must be a power of 2.")

def inflate(self: IntEnumField, resolver: Resolver) -> int:
def inflate(self: IntEnumField, resolver: Resolver) -> enum:
"""Inflate the field.

Args:
Expand Down
4 changes: 2 additions & 2 deletions libdestruct/common/flags/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def __class_getitem__(cls, params: tuple) -> GenericAlias:
python_flag: type[IntFlag]
"""The backing Python IntFlag."""

_backing_type: type[obj]
"""The backing type."""
_backing_type: obj
"""The inflated backing instance."""

lenient: bool
"""Whether the conversion is lenient or not."""
Expand Down
4 changes: 2 additions & 2 deletions libdestruct/common/inflater.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Literal

from libdestruct.backing.memory_resolver import MemoryResolver
from libdestruct.common.type_registry import TypeRegistry
Expand All @@ -21,7 +21,7 @@
class Inflater:
"""The memory manager, which inflates any memory-referencing type."""

def __init__(self: Inflater, memory: MutableSequence, endianness: str = "little") -> None:
def __init__(self: Inflater, memory: MutableSequence, endianness: Literal["little", "big"] = "little") -> None:
"""Initialize the memory manager."""
self.memory = memory
self.endianness = endianness
Expand Down
8 changes: 4 additions & 4 deletions libdestruct/common/obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Generic, TypeVar
from typing import TYPE_CHECKING, Generic, Literal, TypeVar

from libdestruct.common.hexdump import format_hexdump

Expand All @@ -19,7 +19,7 @@
class obj(ABC, Generic[T]):
"""A generic object, with reference to the backing memory view."""

endianness: str = "little"
endianness: Literal["little", "big"] = "little"
"""The endianness of the backing reference view."""

resolver: Resolver
Expand All @@ -31,7 +31,7 @@ class obj(ABC, Generic[T]):
_frozen_value: object = None
"""The frozen value of the object."""

def __init__(self: obj, resolver: Resolver) -> None:
def __init__(self: obj, resolver: Resolver | None) -> None:
"""Initialize a generic object.

Args:
Expand Down Expand Up @@ -59,7 +59,7 @@ def to_bytes(self: obj) -> bytes:
"""Serialize the object to bytes."""

@classmethod
def from_bytes(cls: type[obj], data: bytes, endianness: str = "little") -> obj:
def from_bytes(cls: type[obj], data: bytes, endianness: Literal["little", "big"] = "little") -> obj:
"""Deserialize the object from bytes."""
from libdestruct.libdestruct import inflater

Expand Down
5 changes: 3 additions & 2 deletions libdestruct/common/ptr/ptr.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ def get(self: ptr) -> int:
value = self.resolver.resolve(self.size, 0)
return int.from_bytes(value, self.endianness)

def to_bytes(self: obj) -> bytes:
def to_bytes(self: ptr) -> bytes:
"""Return the serialized representation of the object."""
if self._frozen:
assert isinstance(self._frozen_value, int)
return self._frozen_value.to_bytes(self.size, self.endianness)

return self.resolver.resolve(self.size, 0)
Expand Down Expand Up @@ -159,7 +160,7 @@ def __sub__(self: ptr, n: int) -> ptr:
new_addr = self.get() - n * self._element_size
return ptr(_ArithmeticResolver(self.resolver, new_addr), self.wrapper)

def __getitem__(self: ptr, n: int) -> obj:
def __getitem__(self: ptr, n: int) -> obj | bytes:
"""Return the object at index n relative to this pointer."""
return (self + n).unwrap()

Expand Down
6 changes: 3 additions & 3 deletions libdestruct/common/struct/struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Literal

from libdestruct.common.obj import obj
from libdestruct.common.type_registry import TypeRegistry
Expand All @@ -23,14 +23,14 @@ def __init__(self: struct) -> None:
"""Initialize the struct."""
raise RuntimeError("This type should not be directly instantiated.")

def __new__(cls: type[struct], *args: ..., **kwargs: ...) -> struct: # noqa: PYI034
def __new__(cls: type[struct], *args: Any, **kwargs: Any) -> struct: # noqa: PYI034
"""Create a new struct."""
# Look for an inflater for this struct
type_impl = TypeRegistry().inflater_for(cls)
return type_impl(*args, **kwargs)

@classmethod
def from_bytes(cls: type[struct], data: bytes, endianness: str = "little") -> struct_impl:
def from_bytes(cls: type[struct], data: bytes, endianness: Literal["little", "big"] = "little") -> struct_impl:
"""Create a struct from a serialized representation."""
type_inflater = inflater(data, endianness=endianness)

Expand Down
6 changes: 3 additions & 3 deletions libdestruct/common/struct/struct_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from __future__ import annotations

from types import GenericAlias
from typing import Annotated, get_args, get_origin
from typing import Annotated, Any, get_args, get_origin

from typing_extensions import Self

Expand Down Expand Up @@ -37,7 +37,7 @@ class struct_impl(struct):
_inflater: TypeRegistry = TypeRegistry()
"""The type registry, used for inflating the attributes."""

def __init__(self: struct_impl, resolver: Resolver | None = None, **kwargs: ...) -> None:
def __init__(self: struct_impl, resolver: Resolver | None = None, **kwargs: Any) -> None:
"""Initialize the struct implementation."""
# If we have kwargs and the resolver is None, we provide a fake resolver
if kwargs and resolver is None:
Expand Down Expand Up @@ -80,7 +80,7 @@ def __setattr__(self: struct_impl, name: str, value: object) -> None:
pass
object.__setattr__(self, name, value)

def __new__(cls: struct_impl, *args: ..., **kwargs: ...) -> Self:
def __new__(cls: struct_impl, *args: Any, **kwargs: Any) -> Self:
"""Create a new struct."""
# Skip the __new__ method of the parent class
# struct_impl -> struct -> obj becomes struct_impl -> obj
Expand Down
2 changes: 1 addition & 1 deletion libdestruct/common/union/union.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def freeze(self: union) -> None:
v.freeze()
super().freeze()

def diff(self: union) -> tuple[object, object]:
def diff(self: union) -> tuple[object, object] | dict[str, tuple[object, object]]:
"""Return the difference between the frozen and current value."""
if self._variant is not None:
return self._variant.diff()
Expand Down
4 changes: 2 additions & 2 deletions libdestruct/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from libdestruct.common.type_registry import TypeRegistry

if TYPE_CHECKING: # pragma: no cover
from collections.abc import Generator
from collections.abc import Callable, Generator

from libdestruct.backing.resolver import Resolver
from libdestruct.common.obj import obj
Expand All @@ -26,7 +26,7 @@ def is_field_bound_method(item: obj) -> bool:
return isinstance(item, MethodType) and isinstance(item.__self__, Field)


def size_of(item_or_inflater: obj | callable[[Resolver], obj]) -> int:
def size_of(item_or_inflater: obj | Callable[[Resolver], obj]) -> int:
"""Return the size in bytes of a type, instance, or field descriptor."""
# Field instances (e.g. array_of, ptr_to) — must come before .size check
if isinstance(item_or_inflater, Field):
Expand Down
10 changes: 5 additions & 5 deletions libdestruct/libdestruct.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import mmap
from collections.abc import Sequence
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Literal

from typing_extensions import Self

Expand All @@ -25,7 +25,7 @@
_VALID_ENDIANNESS = ("little", "big")


def inflater(memory: Sequence | mmap.mmap, endianness: str = "little") -> Inflater:
def inflater(memory: Sequence | mmap.mmap, endianness: Literal["little", "big"] = "little") -> Inflater:
"""Return a TypeInflater instance."""
if not isinstance(memory, Sequence | mmap.mmap):
raise TypeError(f"memory must be a Sequence, not {type(memory).__name__}")
Expand All @@ -43,7 +43,7 @@ def __init__(
self: FileInflater,
file_handle: io.BufferedReader,
mmap_obj: mmap.mmap,
endianness: str = "little",
endianness: Literal["little", "big"] = "little",
) -> None:
"""Initialize the file-backed inflater."""
super().__init__(mmap_obj, endianness=endianness)
Expand All @@ -60,7 +60,7 @@ def __exit__(self: FileInflater, *args: object) -> None:
self._file_handle.close()


def inflater_from_file(path: str, writable: bool = False, endianness: str = "little") -> FileInflater:
def inflater_from_file(path: str, writable: bool = False, endianness: Literal["little", "big"] = "little") -> FileInflater:
"""Create an inflater backed by a memory-mapped file.

Args:
Expand All @@ -81,7 +81,7 @@ def inflater_from_file(path: str, writable: bool = False, endianness: str = "lit
return FileInflater(file_handle, mmap_obj, endianness=endianness)


def inflate(item: type, memory: Sequence, address: int | Resolver, endianness: str = "little") -> obj:
def inflate(item: type, memory: Sequence, address: int | Resolver, endianness: Literal["little", "big"] = "little") -> obj:
"""Inflate a memory-referencing type.

Args:
Expand Down
27 changes: 27 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,38 @@ dependencies = ["typing_extensions", "pycparser"]
[project.optional-dependencies]
dev = [
"rich",
"ty",
]

[tool.setuptools.packages.find]
include = ["libdestruct*"]

[tool.ty.src]
exclude = ["test/", "build/"]

[tool.ty.rules]
# -- Dynamic metaclass/descriptor inflation pattern --
# The type registry, struct metaclass, and descriptor protocol resolve attributes
# at runtime in ways that ty cannot yet follow (_type_impl, _member_offsets, etc).
unresolved-attribute = "ignore"

# -- Rules that caught real bugs --
# Keep as warn until annotations are improved, then promote to error.
invalid-return-type = "warn" # union.diff() returns dict, ptr.__getitem__ returns bytes
invalid-argument-type = "warn" # union.__init__ passes None as Resolver
invalid-type-form = "warn" # lowercase `callable`, `iter` as type, `...` as annotation
missing-argument = "warn" # wrong _backing_type: type[obj] (should be obj) in enum/flags
too-many-positional-arguments = "warn" # array.__setitem__ calls set() with wrong arity

# -- Intentional design patterns --
invalid-method-override = "warn" # deliberate param narrowing in _set/set overrides (LSP)
invalid-assignment = "warn" # _backing_type instance vs type confusion; descriptor __set__
unsupported-operator = "warn" # obj.get() returns `object`, operators on result untypeable
call-non-callable = "warn" # dynamic inflater dispatch: resolved_type(resolver)
not-subscriptable = "warn" # object.__setattr__ hides dict types from checker
no-matching-overload = "warn" # untyped dict in struct_parser
invalid-yield = "warn" # yield from dynamic __getitem__ typed as object

[tool.ruff]
line-length = 120
indent-width = 4
Expand Down
Loading