Skip to content

forbidden-function-docstring (PDF616)

Added in 1.0.0 Never fix By default

Part of the PDF rule category.

View source · View documentation source · Search issues

Fix is not available.

What it does

Checks for function and method docstrings on definitions decorated with configured no-docstring decorators.

By default, the no-docstring decorators are the standard overload decorators typing.overload and typing_extensions.overload, including direct import aliases resolved statically by LibCST. Overload variants are type-checker signatures rather than the runtime implementation, so their docstrings are normally ignored by runtime documentation tools. Put user-facing documentation on the implementation function instead.

Decorator names are matched exactly after unwrapping calls. Dotted configured names also match import aliases, so from typing import overload as ov; @ov matches typing.overload. Unqualified configured names are syntactic-only, and pydocformatter does not execute imports or resolve dynamic decorator expressions.

PDF616 reports only existing function or method docstrings. A forbidden-decorator function without a docstring is allowed by this rule and by the PDF6xx missing-owner-docstring rules. Class decorators, dynamic decorators, and non-primary string literals are ignored.

Why is this useful?

Keeping overload documentation on the concrete implementation avoids stale duplicate documentation and matches common docstring tooling behavior.

Ruff compatibility

This rule replaces Ruff's D418.

Examples

A canonical overload variant with a docstring is reported, while the implementation docstring is allowed:

@typing.overload
def parse(value: int) -> int:
    """Parse an integer."""
    pass

def parse(value):
    """Parse a value."""
    return value
PDF616: Line 3: Function decorated with '@typing.overload' should not have a docstring

The standard dotted overload decorators are also matched, and decorator calls are unwrapped:

@typing.overload()
def parse(value: int) -> int:
    """Parse an integer."""
    pass

@typing_extensions.overload
def parse(value: str) -> str:
    """Parse a string."""
    pass

def parse(value):
    """Parse a value."""
    return value
PDF616: Line 3: Function decorated with '@typing.overload' should not have a docstring
PDF616: Line 8: Function decorated with '@typing_extensions.overload' should not have a docstring

Forbidden decorators apply to methods, dunder methods, __init__, and local functions as well as top-level functions:

class Client:
    """Client."""

    @typing.overload
    def connect(self, value: int):
        """Connect an integer."""
        pass

    @typing.overload
    async def fetch(self, value: int):
        """Fetch an integer."""
        pass

    @typing.overload
    def __init__(self, value: int):
        """Initialize from an integer."""
        self.value = value

def outer():
    """Outer function."""

    @typing.overload
    def inner(value: int):
        """Handle an integer."""
        return value
PDF616: Line 6: Function decorated with '@typing.overload' should not have a docstring
PDF616: Line 11: Function decorated with '@typing.overload' should not have a docstring
PDF616: Line 16: Function decorated with '@typing.overload' should not have a docstring
PDF616: Line 24: Function decorated with '@typing.overload' should not have a docstring

Functions decorated with forbidden decorators may omit docstrings:

@typing.overload
def parse(value: int) -> int:
    pass

def parse(value):
    """Parse a value."""
    return value

Unconfigured project-qualified and unresolved alias-like decorator names are not matched:

@project.overload
def parse(value: int):
    """Parse an integer."""
    pass

@t.overload
def build(value: int):
    """Build from an integer."""
    pass

Custom no-docstring decorators can be configured:

docstring-forbidden-function-decorators = ["project.overload", "t.overload"]
@project.overload
def parse(value: int):
    """Parse an integer."""
    pass

@t.overload()
def build(value: int):
    """Build from an integer."""
    pass
PDF616: Line 3: Function decorated with '@project.overload' should not have a docstring
PDF616: Line 8: Function decorated with '@t.overload' should not have a docstring

Dynamic decorators and non-exact dotted names are not matched:

class Client:
    """Client."""

    @decorator_factory(overload)
    def generated(self):
        """Generated method."""
        pass

    @(lambda method: method)
    def dynamic(self):
        """Dynamic method."""
        pass

    @typing.overload.extra
    def extended(self):
        """Extended method."""
        pass

Class docstrings are not function docstrings, even when the class is decorated with a forbidden function decorator:

@overload
class Client:
    """Client."""

If several forbidden decorators are present, the first matching decorator in source order is used in the diagnostic:

@typing.overload
@overload
def parse(value: int):
    """Parse an integer."""
    pass
PDF616: Line 4: Function decorated with '@typing.overload' should not have a docstring

Findings target the docstring's physical lines. This includes compact-suite docstrings, multiline docstrings, and implicitly concatenated docstrings:

@typing.overload
def compact(): """Compact."""

@typing.overload
def multiline():
    """Summary.

    Details.
    """
    pass

@typing.overload
def concatenated():
    (
        "Summary. "
        "Details."
    )
    pass
PDF616: Line 2: Function decorated with '@typing.overload' should not have a docstring
PDF616: Lines 6-9: Function decorated with '@typing.overload' should not have a docstring
PDF616: Lines 15-16: Function decorated with '@typing.overload' should not have a docstring

A suppression must target the docstring lines, not just the decorator or definition line:

@typing.overload  # pydocfmt: ignore[PDF616]
def decorator_suppressed(value: int):
    """Decorator directive does not suppress."""
    pass

@typing.overload
def definition_suppressed(value: int):  # pydocfmt: ignore[PDF616]
    """Definition directive does not suppress."""
    pass

@typing.overload
def docstring_suppressed(value: int):
    """Docstring directive suppresses."""  # pydocfmt: ignore[PDF616]
    pass
PDF616: Line 3: Function decorated with '@typing.overload' should not have a docstring
PDF616: Line 8: Function decorated with '@typing.overload' should not have a docstring

The optional-docstring setting does not make a decorator forbidden. It only affects missing-docstring rules:

docstring-forbidden-function-decorators = []
docstring-optional-function-decorators = ["override"]
@override
def connect():
    """Connect."""
    pass

Options

  • docstring-forbidden-function-decorators: Function decorator names that PDF616 reports when the decorated function has a docstring. Calls are unwrapped before matching, and dotted names also match import aliases resolved statically by LibCST.