add-or-fix-type-checking

작성자: huggingface

ty, make typing, 또는 make check-repo에서 감지된 깨진 타입 검사를 수정합니다. 로컬 실행, CI, 또는 PR 로그에서 타입 오류가 나타날 때 사용하세요.

npx skills add https://github.com/huggingface/transformers --skill add-or-fix-type-checking

Add Or Fix Type Checking

Input

  • <target>: module or directory to type-check (if known).
  • Optional make typing or CI output showing typing failures.

Workflow

  1. Identify scope from the failing run:

    • If you already have make typing or CI output, extract the failing file/module paths.
    • If not, run:
      make typing
      
    • Choose the narrowest target that covers the failures.
  2. Run ty check for the target to get a focused baseline:

    ty check --respect-ignore-files --exclude '**/*_pb*' <target>
    
  3. Triage errors by category before fixing anything:

    • Wrong/missing type annotations on signatures
    • Attribute access on union types (for example X | None)
    • Functions returning broad unions (for example str | list | BatchEncoding)
    • Mixin/protocol self-type issues
    • Dynamic attributes on objects or modules
    • Third-party stub gaps (missing kwargs, missing __version__, etc.)
  4. Apply fixes using this priority order (simplest first):

    a. Narrow unions with isinstance() / if x is None / hasattr(). This is the primary tool for resolving union-type errors. ty narrows through all of these patterns, including the negative forms:

    # Narrow X | None — use `if ...: raise`, never `assert`
    if x is None:
        raise ValueError("x must not be None")
    x.method()  # ty knows x is X here
    
    # Narrow str | UploadFile
    if isinstance(field, str):
        raise TypeError("Expected file upload, got string")
    await field.read()  # ty knows field is UploadFile here
    
    # Narrow broad union parameters early in a function body
    # (common for methods accepting e.g. list | dict | BatchEncoding)
    if isinstance(encoded_inputs, (list, tuple)):
        raise TypeError("Expected a mapping, got sequence")
    encoded_inputs.keys()  # ty sees only the dict/mapping types now
    

    b. Use local variables to help ty track narrowing across closures. When self.x is X | None and you need to pass it to nested functions or closures, ty cannot track that self.x stays non-None. Copy to a local variable and narrow the local:

    manager = self.batching_manager
    if manager is None:
        raise RuntimeError("Manager not initialized")
    # Use `manager` (not `self.batching_manager`) in nested functions
    

    c. Split chained calls when the intermediate type is a broad union. If func().method() fails because func() returns a union, split it:

    # BAD: ty can't narrow through chained calls
    result = func(return_dict=True).to(device)["input_ids"]
    
    # GOOD: split, narrow, then chain
    result = func(return_dict=True)
    if not hasattr(result, "to"):
        raise TypeError("Expected dict-like result")
    inputs = result.to(device)["input_ids"]
    

    d. Fix incorrect type hints at the source. If a parameter is typed X | None but can never be None when actually called, remove None from the hint.

    e. Annotate untyped attributes. Add type annotations to instance variables set in __init__ or elsewhere (for example self.foo: list[int] = []). Declare class-level attributes that are set dynamically later (for example _cache: Cache, _token_tensor: torch.Tensor | None).

    f. Use @overload for methods with input-dependent return types. When a method returns different types based on the input type (e.g. __getitem__ with str vs int keys), use @overload to declare each signature separately:

    from typing import overload
    
    @overload
    def __getitem__(self, item: str) -> ValueType: ...
    @overload
    def __getitem__(self, item: int) -> EncodingType: ...
    @overload
    def __getitem__(self, item: slice) -> dict[str, ValueType]: ...
    
    def __getitem__(self, item: int | str | slice) -> ValueType | EncodingType | dict[str, ValueType]:
        ...  # actual implementation
    

    This eliminates cast() calls at usage sites by giving the checker precise return types for each call pattern.

    g. Make container classes generic to propagate value types. When a class like UserDict holds values whose type changes after transformation (e.g. lists → tensors after .to()), make the class generic so methods can return narrowed types:

    from typing import Generic, overload
    from typing_extensions import TypeVar
    
    _V = TypeVar("_V", default=Any)  # default=Any keeps existing code working
    
    class MyDict(UserDict, Generic[_V]):
        @overload
        def __getitem__(self, item: str) -> _V: ...
        # ...
    
        def to(self, device) -> MyDict[torch.Tensor]:
            # after .to(), values are tensors
            ...
            return self  # type: ignore[return-value]
    

    The default=Any (from typing_extensions) means unparameterized usage like MyDict() stays MyDict[Any] — no existing code needs to change. Only methods that narrow the value type (like .to()) declare a specific return type. This eliminates cast() at all call sites.

    h. Use self: "ProtocolType" for mixins. When a mixin accesses attributes from its host class, define a Protocol in src/transformers/_typing.py and annotate self on methods that need it. Apply this consistently to all methods in the mixin. Import under TYPE_CHECKING to avoid circular imports.

    i. Use TypeGuard functions for dynamic module attributes (for example torch.npu, torch.xpu, torch.compiler). Instead of getattr(torch, "npu") or hasattr(torch, "npu") and torch.npu.is_available(), define a type guard function in src/transformers/_typing.py:

    def has_torch_npu(mod: ModuleType) -> TypeGuard[Any]:
        return hasattr(mod, "npu") and mod.npu.is_available()
    

    Then use it as a narrowing check: if has_torch_npu(torch): torch.npu.device_count(). After the guard, ty treats the module as Any, allowing attribute access without getattr() or cast(). See existing guards in _typing.py for all device backends.

    Key rules for type guards:

    • Use TypeGuard[Any] (not a Protocol) — this is the simplest form that works with ty and avoids losing the original module's known attributes.
    • The guard function must be called directly in an if condition for narrowing to work. ty does NOT narrow through and conditions or if not guard: return.
    • Import guards with from .._typing import has_torch_xxx (not via module attribute _typing.has_torch_xxx) — ty only resolves TypeGuard from direct imports.

    j. Use getattr() / setattr() for dynamic model/config attributes. For runtime-injected fields (for example config/model flags), use getattr(obj, "field", default) for reads and setattr(obj, "field", value) for writes. Also use getattr() for third-party packages missing type stubs (for example getattr(safetensors, "__version__", "unknown")). Avoid getattr(torch, "npu") style — use type guards instead (see above).

    k. Use cast() as a last resort before # type: ignore. Use when you've structurally validated the type but the checker can't see it: pattern-matched AST nodes, known-typed dict values, or validated API responses.

    # After structural validation confirms the type:
    stmt = cast(cst.Assign, node.body[0])
    annotations = cast(list[Annotation], [])
    

    Do not use cast() for module attribute narrowing — use type guards. Do not use cast() when @overload or generics can solve it at the source.

    l. Use # type: ignore only for third-party stub defects. This means cases where the third-party package's type stubs are wrong or incomplete and there is no way to narrow or cast around it. Examples:

    • A kwarg that exists at runtime but is missing from the stubs
    • A method that exists but isn't declared in the stubs Always add the specific error code: # type: ignore[call-arg], not bare # type: ignore.
  5. Things to never do:

    • Never use assert for type narrowing. Asserts are stripped by python -O and must not be relied on for correctness. Use if ...: raise instead.
    • Never use # type: ignore as a first resort. Exhaust all approaches above first.
    • Do not use getattr(torch, "backend") to access dynamic device backends (npu, xpu, hpu, musa, mlu, neuron, compiler) — use type guards
    • Do not use cast() for module attribute narrowing — use type guards
    • Do not use cast() when @overload or generics can eliminate it at the source
    • Do not add helper methods or abstractions just to satisfy the type checker (especially for only 1-2 occurrences)
    • Do not pollute base classes with domain-specific fields; use Protocols
    • Do not add if x is not None guards for values guaranteed non-None by the call chain; fix the annotation instead
    • Do not use conditional inheritance patterns; annotate self instead
  6. Organization:

    • Keep shared Protocols and type aliases in src/transformers/_typing.py
    • Import type-only symbols under if TYPE_CHECKING: to avoid circular deps
    • Use from __future__ import annotations for PEP 604 syntax (X | Y)
  7. Verify and close the PR loop:

    • Re-run ty check on the same <target>
    • Re-run make typing to confirm the type/model-rules step passes
    • If working toward merge readiness, run make check-repo
    • Ensure runtime behavior did not change and run relevant tests
  8. Update CI coverage when adding new typed areas:

    • Update ty_check_dirs in Makefile to include newly type-checked directories.

huggingface의 다른 스킬

sync-models
huggingface
chat-ui의 모델 구성을 HuggingFace 라우터와 동기화 — 새 모델에 대한 설명을 추가하고, 추론 기능이 있는 모델을 표시하며, 32B 이상 모델에 대해 아티팩트를 활성화합니다…
custom-blocks
huggingface
Use when the user has written (or wants to write) a `ModularPipelineBlocks` subclass in a local Python file and needs to package it into a Hub-uploadable…
self-review
huggingface
Use before opening a PR, or whenever asked to self-review a diffusers contribution. Applies the same rubric as the `@claude` CI (checks the diff against…
hf-cloud-sagemaker-production-defaults
huggingface
SageMaker 엔드포인트(실시간 또는 비동기)를 생성하며, 자동 확장, CloudWatch 알람, 태깅이 기본적으로 활성화됩니다. 이 스킬은 엔드포인트를 생성하려고 할 때마다 사용하세요.
hf-cloud-serving-image-selection
huggingface
SageMaker 모델 배포에 적합한 서빙 컨테이너를 선택하고 현재 이미지 URI를 찾습니다. 모델을 배포하려 할 때마다 이 스킬을 사용하세요.
Hugging Face Cli
huggingface
Execute Hugging Face Hub operations using the `hf` CLI. Use when the user needs to download models/datasets/spaces, upload files to Hub repositories, create repos, manage local cache, or run compute jobs on HF infrastructure. Covers authentication, file transfers, repository creation, cache operations, and cloud compute.
Hugging Face Datasets
huggingface
허깅 페이스 허브에서 데이터셋을 생성하고 관리합니다. 리포지토리 초기화, 설정/시스템 프롬프트 정의, 행 업데이트 스트리밍, SQL 기반 데이터셋 쿼리/변환을 지원합니다. 포괄적인 데이터셋 워크플로를 위해 HF MCP 서버와 함께 작동하도록 설계되었습니다.
Hugging Face Evaluation
huggingface
Hugging Face 모델 카드에 평가 결과를 추가하고 관리합니다. README 콘텐츠에서 평가 테이블 추출, Artificial Analysis API에서 점수 가져오기, vLLM/lighteval을 사용한 사용자 정의 모델 평가 실행을 지원합니다. model-index 메타데이터 형식과 함께 작동합니다.