Skip to content

← The Type System as a Design Tool step 5 of 24

Medium Primitives

Literal fit modes and the Final default

df.merge(how="innner") becomes a red squiggle in your editor instead of a runtime ValueError in production — if you are lucky, and if somebody typed the parameter as a Literal.

Literal puts exact values into the type system. Literal["contain"] is a type inhabited by exactly one string. A union of them is a closed set of valid arguments that the checker can verify at every call site, and that assert_never can prove you handled exhaustively.

Legal members: int, str, bytes, bool, enum members, and None. Illegal: floats (Literal[3.14] is a valid-type error), arbitrary expressions, and mutable containers. The float exclusion is deliberate — floating-point equality is not a sound basis for type identity.

The rule that surprises everyone: assignment does not preserve literalness.

mode = "contain"          # inferred: str, NOT Literal['contain']
resize(img, tgt, mode)    # error: str is not Literal['contain', 'cover', 'stretch']

The escape hatch is Final:

DEFAULT_FIT: Final = "contain"        # revealed type: Literal['contain']?
DEFAULT_FIT2: Final[str] = "contain"  # revealed type: str  -- the annotation wins

An explicit type on a Final kills literal inference. TIMEOUT: Final = 30 reveals Literal[30]?; TIMEOUT: Final[int] = 30 reveals int. This problem depends on that: DEFAULT_FIT is declared bare so it can serve as the default value of a Literal-typed parameter.

The task

type Fit = Literal["contain", "cover", "stretch"]

def resize(
    image: tuple[int, int],
    target: tuple[int, int],
    fit: Fit = DEFAULT_FIT,
) -> tuple[int, int]:

image and target are (width, height). Return the new (width, height):

  • contain — scale by min(target_w / w, target_h / h), so the whole image fits inside the target box, preserving aspect ratio.
  • cover — scale by max(...), so the image covers the target box, preserving aspect ratio, overflowing on one axis.
  • stretch — return target unchanged, aspect ratio be damned.

Round each scaled dimension with round(), then clamp it to a minimum of 1: a 1000x1 banner scaled into a 10x10 box would otherwise come back zero pixels tall, and a zero-dimension image is a crash somewhere further down the pipeline.

Close the match with assert_never(fit).

Return a tuple[int, int]. A list will fail — in this mode the container type is part of the contract.

What the tests check

Each mode’s arithmetic; that omitting fit entirely uses DEFAULT_FIT (the Final literal-inference rule, exercised for real); upscaling as well as downscaling; the identity case; and the degenerate aspect ratio that hits the clamp.

Types

Literal is what makes assert_never possible here: the checker knows the parameter has exactly three inhabitants, so a match covering all three leaves Never in the default arm.

One divergence to be aware of, because it will bite you when you move between checkers: mypy does not narrow x in ("contain", "cover") to a Literal. pyright does. If you need to validate an incoming str down to a Fit, write an explicit loop or a chain of == comparisons; the membership test that looks obviously equivalent is not, under mypy.

Loading visualization…