Skip to content

Exempt base-dictated Any positions in @override methods from explicit-any - #4798

Open
nitishagar wants to merge 1 commit into
facebook:mainfrom
nitishagar:fix/explicit-any-override-4548
Open

Exempt base-dictated Any positions in @override methods from explicit-any#4798
nitishagar wants to merge 1 commit into
facebook:mainfrom
nitishagar:fix/explicit-any-override-4548

Conversation

@nitishagar

Copy link
Copy Markdown
Contributor

Fixes #4548

Problem

With explicit-any = "error", a @typing.override method whose base signature is annotated with Any must repeat that Any — narrowing or dropping it would break the override — yet the rule fired on exactly those annotations:

import json
from typing import Any, override

class CustomEncoder(json.JSONEncoder):
    @override
    def default(self, o: Any) -> Any:   # 2 explicit-any errors
        return super().default(o)

The signature is fixed by typeshed's json.JSONEncoder.default ((self, o: Any) -> Any), so the diagnostic points at code that cannot be changed. Ruff exempts @override methods in its equivalent lint (ANN401) for the same reason.

Change

  • The binding step already knows is_override (name-only decorator check) when it registers method annotations; those annotations now carry the method's name when they belong to an @override method.
  • Parameter and return annotations report explicit-any from solve_annotation (which has the class context) instead of expr_untype; a position is exempt only when the annotation is exactly Any and the corresponding base position is explicitly annotated Any:
    • params matched by name (*args/**kwargs and return positions included),
    • async base returns unwrapped from Coroutine[Any, Any, T],
    • every overload of an overloaded base considered.
  • Everything else still reports: undictated positions, methods without @override, @override with no base member (bad-override still fires), base positions that merely lack annotations, and nested Any like list[Any].

The base's own explicit Anys still report when the base is user code in the checked project (in the issue's case the base is in typeshed and not checked) — only the dictated override positions are exempt.

Test plan

  • 12 new testcase!s in pyrefly/lib/test/inference.rs covering: exemption (plain/args-kwargs/overload/typing_extensions/async), still-reporting (undictated positions, no decorator, no base member, unannotated base param, nested Any, per-position granularity)
  • Issue repro checks clean with explicit-any = "error"
  • cargo test -p pyrefly --lib -- --skip test::lsp green (the two missing-source LSP interaction failures are pre-existing on main)
  • Formatting/linting clean

…-any

A method decorated with @typing.override cannot narrow or drop an `Any`
that the base signature already declares — repeating it is required to
keep the override compatible — yet explicit-any fired on exactly those
annotations, pointing at code that cannot be changed (issue facebook#4548; ruff
reached the same conclusion for ANN401).

The override context is known at binding time (where the name-only
is_override check already exists), so the method's annotations now carry
the method name when they belong to an @OverRide method. The solver,
which can resolve the base signature, consumes it: parameter and return
annotations report explicit-any from solve_annotation instead of
expr_untype, exempting a position only when both the annotation is
exactly Any and the corresponding base position (param by name, *args,
**kwargs, or return — unwrapping Coroutine for async bases, considering
every overload) is explicitly annotated Any. Everything else still
reports: undictated positions, missing @OverRide, missing base member,
unannotated base positions, and nested Any like list[Any].

Fixes facebook#4548
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

explicit-any is reported for @override methods that must match a base-class signature annotated with Any

1 participant