Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docutils: Use ClassVar for Directive class variables #11550

Merged
merged 4 commits into from
Mar 9, 2024

Conversation

tony
Copy link
Contributor

@tony tony commented Mar 9, 2024

These variables of Directive are expected and widely accepted in practice as class variables, use ClassVar for these.

Brief example

from docutils.parsers.rst import Directive

class BaseAdmonition(Directive):

    required_arguments = 0
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = {}
    has_content = True

Real world case

Without this, subclasses will error like this in mypy 1.9.0:

src/doctest_docutils.py:...: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
src/doctest_docutils.py:...: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
src/doctest_docutils.py:...: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
OptionSpec = Dict[str, Callable[[str], t.Any]]

class TestsetupDirective(TestDirective):
    """Test setup directive."""

    option_spec: ClassVar[OptionSpec] = {"skipif": directives.unchanged_required}


class TestcleanupDirective(TestDirective):
    """Test cleanup directive."""

    option_spec: ClassVar[OptionSpec] = {"skipif": directives.unchanged_required}


class DoctestDirective(TestDirective):
    """Doctest directive."""

    option_spec: ClassVar[OptionSpec] = {
        "hide": directives.flag,
        "no-trim-doctest-flags": directives.flag,
        "options": directives.unchanged,
        "pyversion": directives.unchanged_required,
        "skipif": directives.unchanged_required,
        "trim-doctest-flags": directives.flag,
    }

Side note: ruff's RUF012

Without debating the merits of the lint rule itself, I've anecdotally had multiple projects using ruff and Directive that have encountered mutable-class-default (RUF012) multiple times.

Further documentation

Docstring for `Directive`

via https://docutils.sourceforge.io/docs/howto/rst-directives.html#the-directive-class (accessed 2024-03-09).

>>> print rst.Directive.__doc__

    Base class for reStructuredText directives.

    The following attributes may be set by subclasses.  They are
    interpreted by the directive parser (which runs the directive
    class):

    - `required_arguments`: The number of required arguments (default:
      0).

    - `optional_arguments`: The number of optional arguments (default:
      0).

    - `final_argument_whitespace`: A boolean, indicating if the final
      argument may contain whitespace (default: False).

    - `option_spec`: A dictionary, mapping known option names to
      conversion functions such as `int` or `float` (default: {}, no
      options).  Several conversion functions are defined in the
      directives/__init__.py module.

      Option conversion functions take a single parameter, the option
      argument (a string or ``None``), validate it and/or convert it
      to the appropriate form.  Conversion functions may raise
      `ValueError` and `TypeError` exceptions.

    - `has_content`: A boolean; True if content is allowed.  Client
      code must handle the case where content is required but not
      supplied (an empty content list will be supplied).

    Arguments are normally single whitespace-separated words.  The
    final argument may contain whitespace and/or newlines if
    `final_argument_whitespace` is True.

    If the form of the arguments is more complex, specify only one
    argument (either required or optional) and set
    `final_argument_whitespace` to True; the client code must do any
    context-sensitive parsing.

    When a directive implementation is being run, the directive class
    is instantiated, and the `run()` method is executed.  During
    instantiation, the following instance variables are set:

    - ``name`` is the directive type or name (string).

    - ``arguments`` is the list of positional arguments (strings).

    - ``options`` is a dictionary mapping option names (strings) to
      values (type depends on option conversion functions; see
      `option_spec` above).

    - ``content`` is a list of strings, the directive content line by line.

    - ``lineno`` is the line number of the first line of the directive.

    - ``content_offset`` is the line offset of the first line of the content from
      the beginning of the current input.  Used when initiating a nested parse.

    - ``block_text`` is a string containing the entire directive.

    - ``state`` is the state which called the directive function.

    - ``state_machine`` is the state machine which controls the state which called
      the directive function.

    Directive functions return a list of nodes which will be inserted
    into the document tree at the point where the directive was
    encountered.  This can be an empty list if there is nothing to
    insert.

    For ordinary directives, the list must contain body elements or
    structural elements.  Some directives are intended specifically
    for substitution definitions, and must return a list of `Text`
    nodes and/or inline elements (suitable for inline insertion, in
    place of the substitution reference).  Such directives must verify
    substitution definition context, typically using code like this::

        if not isinstance(state, states.SubstitutionDef):
            error = state_machine.reporter.error(
                'Invalid context: the "%s" directive can only be used '
                'within a substitution definition.' % (name),
                nodes.literal_block(block_text, block_text), line=lineno)
            return [error]
Example of a `Directive` subclass from docutils documentation

via https://docutils.sourceforge.io/docs/howto/rst-directives.html#admonitions (accessed 2024-03-09).

# Import Docutils document tree nodes module.
from docutils import nodes
# Import Directive base class.
from docutils.parsers.rst import Directive

class BaseAdmonition(Directive):

    required_arguments = 0
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = {}
    has_content = True

    node_class = None
    """Subclasses must set this to the appropriate admonition node class."""

    def run(self):
        # Raise an error if the directive does not have contents.
        self.assert_has_content()
        text = '\n'.join(self.content)
        # Create the admonition node, to be populated by `nested_parse`.
        admonition_node = self.node_class(rawsource=text)
        # Parse the directive contents.
        self.state.nested_parse(self.content, self.content_offset,
                                admonition_node)
        return [admonition_node]

Links

@tony
Copy link
Contributor Author

tony commented Mar 9, 2024

@danieleades Does this change replicate any of your recent docutils PRs? (just making sure I'm not duplicating what you already wrote)

This comment has been minimized.

1 similar comment

This comment has been minimized.

@tony
Copy link
Contributor Author

tony commented Mar 9, 2024

This type change would definitely have downstream ramifications for sphinx.

@sphinx-doc: As downstream consumers of docutils, any opinions or disagreements on Directive using ClassVar here?

This also connects to Sphinx's ignored ruff rule mutable-class-default RUF012 (link).

@danieleades
Copy link
Contributor

@danieleades Does this change replicate any of your recent docutils PRs? (just making sure I'm not duplicating what you already wrote)

it doesn't. In fact it looks like i introduced the issue here - #11523.

Rather than adding ClassVar to the members of each of the Directive subclasses, you should be able to add that to the Directive base class and remove them from all of the subclasses

@tony
Copy link
Contributor Author

tony commented Mar 9, 2024

Rather than adding ClassVar to the members of each of the Directive subclasses, you should be able to add that to the Directive base class and remove them from all of the subclasses

In that case, this change would be beneficial. And to paraphrase, would eliminate the need for subclasses of Directive to annotate ClassVar for the affected members. Is that correct?

@danieleades
Copy link
Contributor

Rather than adding ClassVar to the members of each of the Directive subclasses, you should be able to add that to the Directive base class and remove them from all of the subclasses

In that case, this change would be beneficial. And to paraphrase, would eliminate the need for subclasses of Directive to annotate ClassVar for the affected members. Is that correct?

it should remove the need for the subclass stubs to include those members at all, since they are inherited from the base class

stubs/Pygments/pygments/sphinxext.pyi Outdated Show resolved Hide resolved
stubs/Pygments/pygments/sphinxext.pyi Outdated Show resolved Hide resolved
stubs/Pygments/pygments/sphinxext.pyi Outdated Show resolved Hide resolved

This comment has been minimized.

stubs/Pygments/pygments/sphinxext.pyi Outdated Show resolved Hide resolved
@tony tony force-pushed the improve-docutils-directive branch from 63cf19b to 93e41d9 Compare March 9, 2024 13:05
@tony tony force-pushed the improve-docutils-directive branch from e1e9345 to b3af292 Compare March 9, 2024 13:08

This comment has been minimized.

@tony tony force-pushed the improve-docutils-directive branch 2 times, most recently from 634cf7a to b3af292 Compare March 9, 2024 13:24

This comment has been minimized.

@danieleades
Copy link
Contributor

danieleades commented Mar 9, 2024

I don't think it stop this PR from getting merged, but you might want to see if the maintainers of pygment would be open to adding the type annotations directly to the source?

I see pygments/pygments#1189 was closed because the library still supported python 2.7, but this has now been dropped.

Always better to have the type annotations inline if you possibly can, although you'd probably also want to add a mypy CI target to pygment to make sure they stay correct

This comment has been minimized.

@danieleades
Copy link
Contributor

danieleades commented Mar 9, 2024

Diff from mypy_primer, showing the effect of this PR on open source code:

sphinx (https://github.com/sphinx-doc/sphinx)
+ sphinx/directives/__init__.py: note: In class "ObjectDescription":
+ sphinx/directives/__init__.py:58:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/__init__.py: note: At top level:
+ sphinx/directives/__init__.py: note: In class "DefaultDomain":
+ sphinx/directives/__init__.py:345:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/std/__init__.py: note: In class "Target":
+ sphinx/domains/std/__init__.py:115:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/std/__init__.py: note: In class "Program":
+ sphinx/domains/std/__init__.py:245:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/std/__init__.py: note: In class "Glossary":
+ sphinx/domains/std/__init__.py:309:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/std/__init__.py: note: At top level:
+ sphinx/domains/std/__init__.py: note: In class "ProductionList":
+ sphinx/domains/std/__init__.py:456:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/_object.py: note: In class "PyObject":
+ sphinx/domains/python/_object.py:147:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyFunction":
+ sphinx/domains/python/__init__.py:70:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyVariable":
+ sphinx/domains/python/__init__.py:125:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyClasslike":
+ sphinx/domains/python/__init__.py:164:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyMethod":
+ sphinx/domains/python/__init__.py:192:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyClassMethod":
+ sphinx/domains/python/__init__.py:246:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyStaticMethod":
+ sphinx/domains/python/__init__.py:258:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyAttribute":
+ sphinx/domains/python/__init__.py:286:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyProperty":
+ sphinx/domains/python/__init__.py:331:5: error: Cannot override instance variable (previously declared on base class "PyObject") with class variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyModule":
+ sphinx/domains/python/__init__.py:388:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyCurrentModule":
+ sphinx/domains/python/__init__.py:447:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/todo.py: note: In class "Todo":
+ sphinx/ext/todo.py:56:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/todo.py: note: In class "TodoList":
+ sphinx/ext/todo.py:115:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/rst.py: note: In class "ReSTMarkup":
+ sphinx/domains/rst.py:39:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/rst.py: note: In class "ReSTDirectiveOption":
+ sphinx/domains/rst.py:145:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/javascript.py: note: In class "JSObject":
+ sphinx/domains/javascript.py:49:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/javascript.py: note: In class "JSModule":
+ sphinx/domains/javascript.py:301:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/index.py: note: In class "IndexDirective":
+ sphinx/domains/index.py:71:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/changeset.py: note: In class "VersionChange":
+ sphinx/domains/changeset.py:55:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/cpp/__init__.py: note: In class "CPPObject":
+ sphinx/domains/cpp/__init__.py:68:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/cpp/__init__.py: note: In class "CPPNamespaceObject":
+ sphinx/domains/cpp/__init__.py:387:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/cpp/__init__.py: note: In class "CPPNamespacePushObject":
+ sphinx/domains/cpp/__init__.py:418:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/cpp/__init__.py: note: In class "CPPNamespacePopObject":
+ sphinx/domains/cpp/__init__.py:450:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/cpp/__init__.py: note: In class "CPPAliasObject":
+ sphinx/domains/cpp/__init__.py:627:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/c/__init__.py: note: In class "CObject":
+ sphinx/domains/c/__init__.py:59:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/c/__init__.py: note: In class "CNamespaceObject":
+ sphinx/domains/c/__init__.py:300:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/c/__init__.py: note: In class "CNamespacePushObject":
+ sphinx/domains/c/__init__.py:330:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/c/__init__.py: note: In class "CNamespacePopObject":
+ sphinx/domains/c/__init__.py:361:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/c/__init__.py: note: In class "CAliasObject":
+ sphinx/domains/c/__init__.py:520:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/ifconfig.py: note: In class "IfConfig":
+ sphinx/ext/ifconfig.py:44:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/doctest.py: note: In class "TestsetupDirective":
+ sphinx/ext/doctest.py:146:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/doctest.py: note: In class "TestcleanupDirective":
+ sphinx/ext/doctest.py:152:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/doctest.py: note: In class "DoctestDirective":
+ sphinx/ext/doctest.py:158:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/doctest.py: note: In class "TestcodeDirective":
+ sphinx/ext/doctest.py:169:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/doctest.py: note: In class "TestoutputDirective":
+ sphinx/ext/doctest.py:179:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/autosummary/__init__.py: note: In class "Autosummary":
+ sphinx/ext/autosummary/__init__.py:221:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/patches.py: note: In class "Code":
+ sphinx/directives/patches.py:85:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/patches.py: note: In class "MathDirective":
+ sphinx/directives/patches.py:130:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "Author":
+ sphinx/directives/other.py:182:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "TabularColumns":
+ sphinx/directives/other.py:224:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "Centered":
+ sphinx/directives/other.py:242:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "Acks":
+ sphinx/directives/other.py:265:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "HList":
+ sphinx/directives/other.py:288:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "Only":
+ sphinx/directives/other.py:326:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: At top level:
+ sphinx/directives/code.py: note: In class "Highlight":
+ sphinx/directives/code.py:38:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/code.py: note: In class "CodeBlock":
+ sphinx/directives/code.py:105:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/code.py: note: In class "LiteralInclude":
+ sphinx/directives/code.py:396:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/graphviz.py: note: In class "Graphviz":
+ sphinx/ext/graphviz.py:120:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/graphviz.py: note: In class "GraphvizSimple":
+ sphinx/ext/graphviz.py:189:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/inheritance_diagram.py: note: In class "InheritanceDiagram":
+ sphinx/ext/inheritance_diagram.py:352:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]

alectryon (https://github.com/cpitclaudel/alectryon)
+ alectryon/docutils.py:802: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ alectryon/docutils.py:1120: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]

that's a lot of downstream errors. Might be best to get one of the grand wizards of typeshed to comment on this (@JelleZijlstra, @srittau ?) but i suspect the subclasses in sphinx probably need to add ClassVar annotations to suppress these.

This issue makes me think these errors should be suppressed already, but here we are.

For what it's worth, sphinx is not actually using types-docutils yet, so there's no issue introducing new downstream type errors so long as they're correct

@tony
Copy link
Contributor Author

tony commented Mar 9, 2024

I don't think it stop this PR from getting merged, but you might want to see if the maintainers of pygment would be open to adding the type annotations directly to the source?

I see pygments/pygments#1189 was closed because the library still supported python 2.7, but this has now been dropped.

Always better to have the type annotations inline if you possibly can, although you'd probably also want to add a mypy CI target to pygment to make sure they stay correct

It's a separate topic, in fact I would recommend you create an issue to rekindle interest in them adding annotations (perhaps by starting with typeshed's).

Anecdote: I had to add custom types for pygments recently in another project. Perhaps a lot of users could be impacted by pygments needing types. And of course, better to have them straight from the tap.

@tony
Copy link
Contributor Author

tony commented Mar 9, 2024

that's a lot of downstream errors. Might be best to get one of the grand wizards of typeshed to comment on this (@JelleZijlstra, @srittau ?) but i suspect the subclasses in sphinx probably need to add ClassVar annotations to suppress these.

This issue makes me think these errors should be suppressed already, but here we are

@danieleades My earlier comment: #11550 (comment)

A minor nit: I don't see them as errors, but downstream updates that'd need to be updated on sphinx's side after this change.

@tk0miya, @AA-Turner (via @sphinx-doc):

  • Do you see anything wrong in using ClassVar in Directives as this PR does? It'd require changes on the sphinx side.
  • Can you think of anyone the Sphinx side that'd have a strong opinion on typings who'd like eyes on this?

tony added a commit to git-pull/gp-libs that referenced this pull request Mar 9, 2024
src/doctest_docutils.py:145: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
src/doctest_docutils.py:151: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
src/doctest_docutils.py:157: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
Found 3 errors in 1 file (checked 11 source files)

See also: python/typeshed#11550
@danieleades
Copy link
Contributor

A minor nit: I don't see them as errors, but downstream updates that'd need to be updated on sphinx's side after this change.

i agree

tony added a commit to git-pull/gp-libs that referenced this pull request Mar 9, 2024
src/doctest_docutils.py:145: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
src/doctest_docutils.py:151: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
src/doctest_docutils.py:157: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
Found 3 errors in 1 file (checked 11 source files)

See also: python/typeshed#11550
tony added a commit to git-pull/gp-libs that referenced this pull request Mar 9, 2024
src/doctest_docutils.py:145: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
src/doctest_docutils.py:151: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
src/doctest_docutils.py:157: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
Found 3 errors in 1 file (checked 11 source files)

See also: python/typeshed#11550
tony added a commit to git-pull/gp-libs that referenced this pull request Mar 9, 2024
src/doctest_docutils.py:145: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
src/doctest_docutils.py:151: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
src/doctest_docutils.py:157: error: Cannot override instance variable (previously declared on base class "Directive") with class variable  [misc]
Found 3 errors in 1 file (checked 11 source files)

See also: python/typeshed#11550
@tony tony force-pushed the improve-docutils-directive branch from b3af292 to 2997283 Compare March 9, 2024 16:01

This comment has been minimized.

@danieleades
Copy link
Contributor

Hopefully this gets merged soon. This fixes some downstream errors in my own repos that i inadvertently introduced in #11523

tony and others added 4 commits March 9, 2024 11:32
stubs/Pygments/pygments/sphinxext.pyi:11-15: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
`Directives` adding these as `ClassVar` eliminates the
need for its subclasses to.

Co-authored-by: danieleades <[email protected]>
@tony tony force-pushed the improve-docutils-directive branch from 2997283 to c8f0c22 Compare March 9, 2024 17:32
@tony
Copy link
Contributor Author

tony commented Mar 9, 2024

@danieleades @JelleZijlstra @srittau Rebased at 5b1fd12

Copy link
Contributor

github-actions bot commented Mar 9, 2024

Diff from mypy_primer, showing the effect of this PR on open source code:

sphinx (https://github.com/sphinx-doc/sphinx)
+ sphinx/directives/__init__.py: note: In class "ObjectDescription":
+ sphinx/directives/__init__.py:58:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/__init__.py: note: At top level:
+ sphinx/directives/__init__.py: note: In class "DefaultDomain":
+ sphinx/directives/__init__.py:345:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/std/__init__.py: note: In class "Target":
+ sphinx/domains/std/__init__.py:115:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/std/__init__.py: note: In class "Program":
+ sphinx/domains/std/__init__.py:245:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/std/__init__.py: note: In class "Glossary":
+ sphinx/domains/std/__init__.py:309:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/std/__init__.py: note: At top level:
+ sphinx/domains/std/__init__.py: note: In class "ProductionList":
+ sphinx/domains/std/__init__.py:456:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/_object.py: note: In class "PyObject":
+ sphinx/domains/python/_object.py:147:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyFunction":
+ sphinx/domains/python/__init__.py:70:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyVariable":
+ sphinx/domains/python/__init__.py:125:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyClasslike":
+ sphinx/domains/python/__init__.py:164:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyMethod":
+ sphinx/domains/python/__init__.py:192:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyClassMethod":
+ sphinx/domains/python/__init__.py:246:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyStaticMethod":
+ sphinx/domains/python/__init__.py:258:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyAttribute":
+ sphinx/domains/python/__init__.py:286:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyProperty":
+ sphinx/domains/python/__init__.py:331:5: error: Cannot override instance variable (previously declared on base class "PyObject") with class variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyModule":
+ sphinx/domains/python/__init__.py:388:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/python/__init__.py: note: In class "PyCurrentModule":
+ sphinx/domains/python/__init__.py:447:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/todo.py: note: In class "Todo":
+ sphinx/ext/todo.py:56:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/todo.py: note: In class "TodoList":
+ sphinx/ext/todo.py:115:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/rst.py: note: In class "ReSTMarkup":
+ sphinx/domains/rst.py:39:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/rst.py: note: In class "ReSTDirectiveOption":
+ sphinx/domains/rst.py:145:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/javascript.py: note: In class "JSObject":
+ sphinx/domains/javascript.py:49:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/javascript.py: note: In class "JSModule":
+ sphinx/domains/javascript.py:301:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/index.py: note: In class "IndexDirective":
+ sphinx/domains/index.py:71:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/changeset.py: note: In class "VersionChange":
+ sphinx/domains/changeset.py:55:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/cpp/__init__.py: note: In class "CPPObject":
+ sphinx/domains/cpp/__init__.py:68:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/cpp/__init__.py: note: In class "CPPNamespaceObject":
+ sphinx/domains/cpp/__init__.py:387:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/cpp/__init__.py: note: In class "CPPNamespacePushObject":
+ sphinx/domains/cpp/__init__.py:418:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/cpp/__init__.py: note: In class "CPPNamespacePopObject":
+ sphinx/domains/cpp/__init__.py:450:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/cpp/__init__.py: note: In class "CPPAliasObject":
+ sphinx/domains/cpp/__init__.py:627:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/c/__init__.py: note: In class "CObject":
+ sphinx/domains/c/__init__.py:59:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/c/__init__.py: note: In class "CNamespaceObject":
+ sphinx/domains/c/__init__.py:300:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/c/__init__.py: note: In class "CNamespacePushObject":
+ sphinx/domains/c/__init__.py:330:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/c/__init__.py: note: In class "CNamespacePopObject":
+ sphinx/domains/c/__init__.py:361:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/domains/c/__init__.py: note: In class "CAliasObject":
+ sphinx/domains/c/__init__.py:520:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/ifconfig.py: note: In class "IfConfig":
+ sphinx/ext/ifconfig.py:44:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/doctest.py: note: In class "TestsetupDirective":
+ sphinx/ext/doctest.py:146:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/doctest.py: note: In class "TestcleanupDirective":
+ sphinx/ext/doctest.py:152:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/doctest.py: note: In class "DoctestDirective":
+ sphinx/ext/doctest.py:158:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/doctest.py: note: In class "TestcodeDirective":
+ sphinx/ext/doctest.py:169:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/doctest.py: note: In class "TestoutputDirective":
+ sphinx/ext/doctest.py:179:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/autosummary/__init__.py: note: In class "Autosummary":
+ sphinx/ext/autosummary/__init__.py:221:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/patches.py: note: In class "Code":
+ sphinx/directives/patches.py:85:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/patches.py: note: In class "MathDirective":
+ sphinx/directives/patches.py:130:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "Author":
+ sphinx/directives/other.py:182:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "TabularColumns":
+ sphinx/directives/other.py:224:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "Centered":
+ sphinx/directives/other.py:242:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "Acks":
+ sphinx/directives/other.py:265:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "HList":
+ sphinx/directives/other.py:288:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: In class "Only":
+ sphinx/directives/other.py:326:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/other.py: note: At top level:
+ sphinx/directives/code.py: note: In class "Highlight":
+ sphinx/directives/code.py:38:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/code.py: note: In class "CodeBlock":
+ sphinx/directives/code.py:105:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/directives/code.py: note: In class "LiteralInclude":
+ sphinx/directives/code.py:396:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/graphviz.py: note: In class "Graphviz":
+ sphinx/ext/graphviz.py:120:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/graphviz.py: note: In class "GraphvizSimple":
+ sphinx/ext/graphviz.py:189:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ sphinx/ext/inheritance_diagram.py: note: In class "InheritanceDiagram":
+ sphinx/ext/inheritance_diagram.py:352:5: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]

alectryon (https://github.com/cpitclaudel/alectryon)
+ alectryon/docutils.py:802: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]
+ alectryon/docutils.py:1120: error: Cannot override class variable (previously declared on base class "Directive") with instance variable  [misc]

@JelleZijlstra
Copy link
Member

That's some scare mypy-primer output, but it sounds like it's expected.

@JelleZijlstra JelleZijlstra merged commit 96e62dd into python:main Mar 9, 2024
43 checks passed
@tony tony deleted the improve-docutils-directive branch March 10, 2024 01:07
tony added a commit to git-pull/gp-libs that referenced this pull request Mar 10, 2024
@adamtheturtle
Copy link
Contributor

adamtheturtle commented Mar 12, 2024

FWIW this has caused type errors in my project.

I subclass sphinx.directives.code.CodeBlock, and I override option_spec:

class SubstitutionCodeBlock(CodeBlock):
    """
    Similar to CodeBlock but replaces placeholders with variables.
    """

    option_spec = CodeBlock.option_spec
    option_spec["substitutions"] = directives.flag

and I get the following error:

Cannot override instance variable (previously declared on base class "CodeBlock") with class variable

@danieleades
Copy link
Contributor

FWIW this has caused type errors in my project.

I subclass sphinx.directives.code.CodeBlock, and II override option_spec:

class SubstitutionCodeBlock(CodeBlock):
    """
    Similar to CodeBlock but replaces placeholders with variables.
    """

    option_spec = CodeBlock.option_spec
    option_spec["substitutions"] = directives.flag

and I get the following error:

Cannot override instance variable (previously declared on base class "CodeBlock") with class variable

this is an issue in Sphinx. The class in Sphinx should be annotating the variable with typing.ClassVar, but is not. Sphinx uses Ruff for linting, but has suppressed the lint for this.

You may wish to raise a PR or issue in the Sphinx repo

@jaraco
Copy link
Member

jaraco commented Mar 31, 2024

This issue led to a failure downstream in jaraco/jaraco.packaging#14.

jaraco added a commit to jaraco/jaraco.packaging that referenced this pull request Mar 31, 2024
Declare ``SidebarLinksDirective.has_content`` as a class variable to align with changes made in python/typeshed#11550. Closes #14.
@danieleades
Copy link
Contributor

This issue led to a failure downstream in jaraco/jaraco.packaging#14.

The solution is to annotate the class variables in the subclass using ClassVar

@jaraco
Copy link
Member

jaraco commented Mar 31, 2024

This issue led to a failure downstream in jaraco/jaraco.packaging#14.

The solution is to annotate the class variables in the subclass using ClassVar

And indeed, that's what I've done. It was a little annoying that this enhanced specification broke the build. I'm also a little worried that it's over constraining. After all, a subclass could customize the instance attribute and that still would have the intended effect but wouldn't pass static analysis. But I guess that's not my problem, so I'll leave it for someone else to adjudicate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants