NAM103: a predicate that doesn't return a bool¶
Junior¶
A name that starts with is_, has_, can_, should_, was_ or check_ asks a yes-or-no question. Callers write if is_ready(): and expect True or False.
def is_admin(user) -> str: # NAM103: returns a string
return user.role
def is_admin(user) -> bool: # answers the question
return user.role == "admin"
When a predicate returns something else, such as a string or a number, Python still treats it as true or false. An empty string and zero count as false. Every other value counts as true. So the code works until the day the value is empty.
To fix it, return a bool. If the function really returns a value, rename it after the value: role_of(user), admin_role(user).
Senior¶
The bug from a predicate that returns a value shows up in a caller, far from the function, and only for one input: the empty string, zero, the empty list. That is what makes it worth a check.
namelint reads the return annotation when there is one, and the return statements when there isn't. It never infers types, so when it can't tell, it stays quiet:
return self._compute()could return anything.-> Anyand-> objectsay the author doesn't know either.-> TypeGuard[X]and-> TypeIs[X]are bools.return Noneis an early exit in most predicates.- A
check_annotated-> Nonereports a problem by raising, which is a valid way to answer.
A name whose last word names the returned type is a factory, and stays quiet: is_instance_schema(cls) -> IsInstanceSchema builds a schema. Whole words only, so is_valid() -> ValidationResult is still reported.
A check_ that returns a result object is a common case where the right fix is the name: run_check, dns_check, spf_record.
Expert¶
The annotation is read first, string forward references included. It is reported, unparsed, as the evidence unless it is bool, a TypeGuard[...] or TypeIs[...], or mentions Any or object anywhere: -> dict[str, Any] | bool stays quiet. Without an annotation, the first return whose value is evidently not a bool is the evidence: a non-bool constant, an f-string, a container, a lambda. Calls, names and attributes are unknown and stay quiet.
A generator under a predicate name is NAM107's finding and is skipped here. A quoted return value is cut at 40 characters in the message.
check_ joined the predicate prefixes in 0.2.0. Since namelint must pass its own lint, its own checks are named find_*.
This is the quietest check in the corpus: 10 findings in CPython's 51,726 functions, and one in pytest. In the first measured run both findings were genuine.