chore: type checking pattern folder#932
Open
xieofxie wants to merge 6 commits into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cleans the
patternfolder (includingop_input_gen) to 0 mypy errors under the strict[tool.mypy]config and addswinml.modelkit.patternto the required "Type check (required)" gate inlint.yml(now 24 packages, 342 source files).The cardinal preference throughout: narrow before you widen — fix at the source, not the call site. Two places deviate from that and instead carry a deliberate, documented choice. They're called out below because they're the non-obvious ones a reviewer should weigh in on.
Design decision 1 —
object(notAny) value type forget_input_and_infinite_attribute_combinationsThe op input generators return heterogeneous combination dicts:
Values are a mix of
InputShapeConstraint,InputValueConstraint,int,None, etc. — there is no single common base. The choice was betweendict[str, Any]anddict[str, object];objectwas chosen (the more type-safe option):objectforces every consumer to narrow withisinstance/castbefore dereferencing — mypy will not let you call.shape/.valueon a bareobject. That's the point: the dict is genuinely heterogeneous, so the read sites should be explicit about which variant they expect.Anywould silently disable checking at every read site — a value typedAnyaccepts any attribute access, so a real bug (wrong key, wrong variant) survives type-checking.Cost of
object(worth knowing): becausedictis invariant in its value type,dict[str, object]is not interchangeable withdict[str, InputConstraint]. That forced explicit local annotations (combinations: list[dict[str, object]] = [...]) andisinstance/castnarrowing at the consumption sites across ~48 generator files. We accepted that churn in exchange for the consumers staying honest.Why not a precise union —
InputConstraint | int | None | …?InputConstraintis a real ABC base (coveringInputValueConstraint/InputShapeConstraint/VariadicInputConstraint), so a union looks tempting. It loses toobjectfor four reasons:axisisint, other ops putfloat/bool/str/ lists, andget_finite_attribute_setsis genuinely typeddict[str, list[Any]]. A correct union would have to enumerate every scalar any of ~48 generators ever inserts, and stay synced forever; miss one and that generator stops type-checking.objectis the honest "this set is open."InputConstraint | int | None, mypy still rejectsv.shapewithout narrowing (int/Nonehave no.shape), so every consumer still needsisinstance/cast. The union only tightens the write side; the read side — where the bulk of the burden is — is identical toobject.dictvalue-invariance makes a union interoperate with nothing.dict[str, InputConstraint | int | None]is its own invariant type — not assignable todict[str, object]nordict[str, InputConstraint].objectis at least the conventional sink general-purpose helpers are written against.type A = …alias has a literal-inference catch. Underlist[dict[str, A]], mypy checks each dict-literal value againstA; the first generator with a value outsideAerrors at that literal, making the alias a perpetually-tightened bottleneck across 48 files.objectaccepts every literal with zero per-file coupling.In short: if these dicts held only constraints,
dict[str, InputConstraint]would be the correct tight type. They mix constraints with open-ended scalar attribute values, so the real choice isAnyvsobject— andobjectwins by forcing honest narrowing.A
Sequence[Mapping[str, object]]return (covariant, read-only) was also considered and is arguably cleaner, but it's a broader change to the consumer contracts; deferred to keep this PR a typing-only pass.Design decision 2 —
# type: ignore[misc]on the dynamic base class ingelu_patterns.pyget_runtime_checker_op("Gelu")is a runtime registry lookup that returnstype[OpInputGenerator]. mypy cannot model a class whose base is a runtime call expression — it can't compute the MRO / inherited members /super()resolution at definition time. This is the documented "Unsupported dynamic base class" limitation;[misc]is the error code for it.Casting does not help (verified):
get_runtime_checker_op("Gelu")directlyUnsupported dynamic base class [misc]cast("type[OpInputGenerator]", ...)as baseUnsupported dynamic base class "cast" [misc]_Base: type[OpInputGenerator] = ...Variable "_Base" is not valid as a type+Invalid base class [misc]The return type is already precisely
type[OpInputGenerator]— there is nothing to cast. The[misc]is about using a runtime value as a base class at all, not about its type being unknown.cast()is itself a call expression, so it's rejected identically.The only way to remove the ignore is to drop the dynamic base and inherit from the concrete class statically (
from .op_input_gen.unary_like_input_generator import GeluInputGenerator). That was rejected on purpose: it couples the pattern module to a concrete class + import path and abandons the registry's late-binding and domain resolution (get_runtime_checker_op("Gelu", domain="com.microsoft")→ a different class). The registry indirection is the intended architecture, so the narrow, single-line# type: ignore[misc]— with an inline reason — is the correct handling.